Springboot下Shiro+Token使用redis做安全认证方案
以前项目中权限认证没有使用安全框架,都是在自定义filter中判断是否登录以及用户是否有操作权限的。 最近开了新项目,搭架子时,想到使用安全框架来解决认证问题,spring security太过庞大,我们的项目不大,所以决定采用Shiro
什么是Shiro Apache Shiro 是一个强大灵活的开源安全框架,可以完全处理身份验证、授权、加密和会话管理。
Realm是Shiro的核心组建,也一样是两步走,认证和授权,在Realm中的表现为以下两个方法。
认证:doGetAuthenticationInfo,核心作用判断登录信息是否正确
授权:doGetAuthorizationInfo,核心作用是获取用户的权限字符串,用于后续的判断
Shiro过滤器 当 Shiro 被运用到 web 项目时,Shiro 会自动创建一些默认的过滤器对客户端请求进行过滤。以下是 Shiro 提供的部分过滤器:
过滤器
描述
anon
表示可以匿名使用
authc
表示需要认证(登录)才能使用
authcBasic
表示httpBasic认证
perms
当有多个参数时必须每个参数都通过才通过 perms[“user:add:”]
port
port[8081] 跳转到schemal://serverName:8081?queryString
rest
权限
roles
角色
ssl
表示安全的url请求
user
表示必须存在用户,当登入操作时不做检查
为什么选择shiro
简单性,Shiro 在使用上较 Spring Security 更简单,更容易理解。
灵活性,Shiro 可运行在 Web、EJB、IoC、Google App Engine 等任何应用环境,却不依赖这些环境。而 Spring Security 只能与 Spring 一起集成使用。
可插拔,Shiro 干净的 API 和设计模式使它可以方便地与许多的其它框架和应用进行集成。Shiro 可以与诸如 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 这类第三方框架无缝集成。Spring Security 在这方面就显得有些捉衿见肘。
spring boot整合shiro 添加maven依赖 在项目中引入shiro非常简单,我们只需要引入 shiro-pring 就可以了
1 2 3 4 5 6 7 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > 1.4.0</version > </dependency >
shiro自定义认证token AuthenticationToken 用于收集用户提交的身份(如用户名)及凭据(如密码)。Shiro会调用CredentialsMatcher对象的doCredentialsMatch方法对AuthenticationInfo对象和AuthenticationToken进行匹配。匹配成功则表示主体(Subject)认证成功,否则表示认证失败。
Shiro 仅提供了一个可以直接使用的 UsernamePasswordToken,用于实现基于用户名/密码主体(Subject)身份认证。UsernamePasswordToken实现了 RememberMeAuthenticationToken 和 HostAuthenticationToken,可以实现“记住我”及“主机验证”的支持。
我们的业务逻辑是每次调用接口,不使用session存储登录状态,使用在head里面存token的方式,所以不使用session,并不需要用户密码认证。
自定义token如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class YtoooToken implements AuthenticationToken { private String token; public YtoooToken (String token) { this .token = token; } @Override public Object getPrincipal () { return token; } @Override public Object getCredentials () { return token; } }
shiro自定义Realm Realm是shiro的核心组件,主要处理两大功能:
认证 我们接收filter传过来的token,并认证login操作的token
授权 获取到登录用户信息,并取得用户的权限存入roles,以便后期对接口进行操作权限验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Slf4j public class UserRealm extends AuthorizingRealm { @Autowired private JedisClusterClient jedis; @Override public boolean supports (AuthenticationToken token) { return token instanceof YtoooToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principals) { log.info("Shiro权限配置" ); String token = principals.toString(); UserDetailVO userDetailVO = JSON.parseObject(jedis.get(token), UserDetailVO.class); Set<String> roles = new HashSet<>(); roles.add(userDetailVO.getAuthType() + "" ); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roles); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { log.info("Shiror认证" ); YtoooToken usToken = (YtoooToken) token; String sid = (String) usToken.getCredentials(); if (StringUtils.isBlank(sid)) { return null ; } log.info("sid: " + sid); return new SimpleAccount(sid, sid, "userRealm" ); } }
shiro自定义拦截器 自定义shiro拦截器来控制指定请求的访问权限,并登录shiro以便认证
我们自定义shiro拦截器主要使用其中的两个方法:
isAccessAllowed() 判断是否可以登录到系统
onAccessDenied() 当isAccessAllowed()返回false时,登录被拒绝,进入此接口进行异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Slf4j public class TokenFilter extends FormAuthenticationFilter { private String errorCode; private String errorMsg; private static JedisClusterClient jedis = JedisClusterClient.getInstance(); @Override protected boolean isAccessAllowed (ServletRequest request, ServletResponse response, Object mappedValue) { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String sid = httpServletRequest.getHeader("sid" ); if (StringUtils.isBlank(sid)) { this .errorCode = ResponseEnum.TOKEN_UNAVAILABLE.getCode(); this .errorMsg = ResponseEnum.TOKEN_UNAVAILABLE.getMessage(); return false ; } log.info("sid: " + sid); UserDetailVO userInfo = null ; try { userInfo = JSON.parseObject(jedis.get(sid), UserDetailVO.class); } catch (Exception e) { this .errorCode = ResponseEnum.TOKEN_EXPIRE.getCode(); this .errorMsg = ResponseEnum.TOKEN_EXPIRE.getMessage(); return false ; } if (userInfo == null ) { this .errorCode = ResponseEnum.TOKEN_EXPIRE.getCode(); this .errorMsg = ResponseEnum.TOKEN_EXPIRE.getMessage(); return false ; } jedis.expire(sid, 30 * 60 ); YtoooToken token = new YtoooToken(sid); getSubject(request, response).login(token); return true ; } @Override protected boolean onAccessDenied (ServletRequest request, ServletResponse response) { ResponseMessage result = Result.error(this .errorCode,this .errorMsg); String reponseJson = (new Gson()).toJson(result); response.setContentType("application/json; charset=utf-8" ); response.setCharacterEncoding("utf-8" ); ServletOutputStream outputStream = null ; try { outputStream = response.getOutputStream(); outputStream.write(reponseJson.getBytes()); } catch (IOException e) { log.error("权限校验异常" ,e); } finally { if (outputStream != null ){ try { outputStream.flush(); outputStream.close(); } catch (IOException e) { log.error("权限校验,关闭连接异常" ,e); } } } return false ; } }
配置ShiroConfig springboot中,组件通过@Bean的方式交由spring统一管理,在这里需要配置 securityManager,shiroFilter,AuthorizationAttributeSourceAdvisor
注入realm 1 2 3 4 5 6 @Bean public UserRealm userRealm () { UserRealm userRealm = new UserRealm(); return userRealm; }
注入 securityManager 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Bean("securityManager") public DefaultWebSecurityManager getManager (UserRealm realm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(realm); DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false ); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); manager.setSubjectDAO(subjectDAO); return manager; }
注入 shiroFilter 此处将自定义过滤器添加到shiro中,并配置具体哪些路径,执行shiro的那些过滤规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Bean("shiroFilter") public ShiroFilterFactoryBean factory (DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); Map<String, Filter> filterMap = new HashMap<>(); filterMap.put("token" , new TokenFilter()); factoryBean.setFilters(filterMap); factoryBean.setSecurityManager(securityManager); Map<String, String> filterRuleMap = new HashMap<>(); filterRuleMap.put("/swagger-ui.html" , "anon" ); filterRuleMap.put("/**/*.js" , "anon" ); filterRuleMap.put("/**/*.png" , "anon" ); filterRuleMap.put("/**/*.ico" , "anon" ); filterRuleMap.put("/**/*.css" , "anon" ); filterRuleMap.put("/**/ui/**" , "anon" ); filterRuleMap.put("/**/swagger-resources/**" , "anon" ); filterRuleMap.put("/**/api-docs/**" , "anon" ); filterRuleMap.put("/login/login" , "anon" ); filterRuleMap.put("/login/verifyCode" , "anon" ); filterRuleMap.put("/**" , "token" ); factoryBean.setFilterChainDefinitionMap(filterRuleMap); return factoryBean;
配置DefaultAdvisorAutoProxyCreator 解决 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。
1 2 3 4 5 6 7 8 9 10 11 @Bean public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator () { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setUsePrefix(true ); return defaultAdvisorAutoProxyCreator; }
配置 AuthorizationAttributeSourceAdvisor 使doGetAuthorizationInfo()Shiro权限配置生效 1 2 3 4 5 6 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor (DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }
在接口中控制权限 使用RequiresRoles注解来配置该接口需要的权限
当配置logical = Logical.OR时,登录这配置的权限在1,2,3中任意一个,既可以成功访问接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @ApiOperation("任务调度") @PostMapping("/dispatch") @RequiresRoles(value = { "1", "2", "3" }, logical = Logical.OR) public ResponseMessage dispatch (@RequestBody @Valid DispatchVO dispatchVO) { log.info("任务调度开始 入参:" + JSON.toJSONString(dispatchVO)); try { service.dispatch(dispatchVO); return Result.success(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getMessage()); } catch (RuntimeException e) { log.error("任务调度失败" , e); return Result.error(ResponseEnum.ERROR.getCode(), e.getMessage()); } catch (Exception e) { log.error("任务调度失败" , e); return Result.error(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getMessage()); } }
统一的异常处理 配置全局异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @ControllerAdvice @Order(value=1) public class ShiroExceptionAdvice { private static final Logger logger = LoggerFactory.getLogger(ShiroExceptionAdvice.class); @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler({AuthenticationException.class, UnknownAccountException.class, UnauthenticatedException.class, IncorrectCredentialsException.class}) @ResponseBody public ResponseMessage unauthorized (Exception exception) { logger.warn(exception.getMessage(), exception); logger.info("catch UnknownAccountException" ); return Result.error(ResponseEnum.NOT_AUTHORIZED.getCode(), ResponseEnum.NOT_AUTHORIZED.getMessage()); } @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler(UnauthorizedException.class) @ResponseBody public ResponseMessage unauthorized1 (UnauthorizedException exception) { logger.warn(exception.getMessage(), exception); return Result.error(ResponseEnum.NOT_AUTHORIZED.getCode(), ResponseEnum.NOT_AUTHORIZED.getMessage()); } }
上面使用的redis工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Bean @DependsOn("ConfigUtil") public JedisClusterClient getClient () { ml.ytooo.redis.RedisProperties.expireSeconds = redisProperties.getExpireSeconds(); ml.ytooo.redis.RedisProperties.clusterNodes = redisProperties.getClusterNodes(); ml.ytooo.redis.RedisProperties.connectionTimeout = redisProperties.getConnectionTimeout(); ml.ytooo.redis.RedisProperties.soTimeout = redisProperties.getSoTimeout(); ml.ytooo.redis.RedisProperties.maxAttempts = redisProperties.getMaxAttempts(); if (StringUtils.isNotBlank(redisProperties.password)) { ml.ytooo.redis.RedisProperties.password = redisProperties.password; }else { ml.ytooo.redis.RedisProperties.password = null ; } return JedisClusterClient.getInstance(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 @Data @Component @ConfigurationProperties(prefix = "redis.cache") public class RedisProperties { private int expireSeconds; private String clusterNodes; private int connectionTimeout; private String password; private int soTimeout; private int maxAttempts; }
依赖工具集:
以前项目中权限认证没有使用安全框架,都是在自定义filter中判断是否登录以及用户是否有操作权限的。 最近开了新项目,搭架子时,想到使用安全框架来解决认证问题,spring security太过庞大,我们的项目不大,所以决定采用Shiro
什么是Shiro Apache Shiro 是一个强大灵活的开源安全框架,可以完全处理身份验证、授权、加密和会话管理。
Realm是Shiro的核心组建,也一样是两步走,认证和授权,在Realm中的表现为以下两个方法。
认证:doGetAuthenticationInfo,核心作用判断登录信息是否正确
授权:doGetAuthorizationInfo,核心作用是获取用户的权限字符串,用于后续的判断
Shiro过滤器 当 Shiro 被运用到 web 项目时,Shiro 会自动创建一些默认的过滤器对客户端请求进行过滤。以下是 Shiro 提供的部分过滤器:
过滤器
描述
anon
表示可以匿名使用
authc
表示需要认证(登录)才能使用
authcBasic
表示httpBasic认证
perms
当有多个参数时必须每个参数都通过才通过 perms[“user:add:”]
port
port[8081] 跳转到schemal://serverName:8081?queryString
rest
权限
roles
角色
ssl
表示安全的url请求
user
表示必须存在用户,当登入操作时不做检查
为什么选择shiro
简单性,Shiro 在使用上较 Spring Security 更简单,更容易理解。
灵活性,Shiro 可运行在 Web、EJB、IoC、Google App Engine 等任何应用环境,却不依赖这些环境。而 Spring Security 只能与 Spring 一起集成使用。
可插拔,Shiro 干净的 API 和设计模式使它可以方便地与许多的其它框架和应用进行集成。Shiro 可以与诸如 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 这类第三方框架无缝集成。Spring Security 在这方面就显得有些捉衿见肘。
spring boot整合shiro 添加maven依赖 在项目中引入shiro非常简单,我们只需要引入 shiro-pring 就可以了
1 2 3 4 5 6 7 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > 1.4.0</version > </dependency >
shiro自定义认证token AuthenticationToken 用于收集用户提交的身份(如用户名)及凭据(如密码)。Shiro会调用CredentialsMatcher对象的doCredentialsMatch方法对AuthenticationInfo对象和AuthenticationToken进行匹配。匹配成功则表示主体(Subject)认证成功,否则表示认证失败。
Shiro 仅提供了一个可以直接使用的 UsernamePasswordToken,用于实现基于用户名/密码主体(Subject)身份认证。UsernamePasswordToken实现了 RememberMeAuthenticationToken 和 HostAuthenticationToken,可以实现“记住我”及“主机验证”的支持。
我们的业务逻辑是每次调用接口,不使用session存储登录状态,使用在head里面存token的方式,所以不使用session,并不需要用户密码认证。
自定义token如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class YtoooToken implements AuthenticationToken { private String token; public YtoooToken (String token) { this .token = token; } @Override public Object getPrincipal () { return token; } @Override public Object getCredentials () { return token; } }
shiro自定义Realm Realm是shiro的核心组件,主要处理两大功能:
认证 我们接收filter传过来的token,并认证login操作的token
授权 获取到登录用户信息,并取得用户的权限存入roles,以便后期对接口进行操作权限验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Slf4j public class UserRealm extends AuthorizingRealm { @Autowired private JedisClusterClient jedis; @Override public boolean supports (AuthenticationToken token) { return token instanceof YtoooToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principals) { log.info("Shiro权限配置" ); String token = principals.toString(); UserDetailVO userDetailVO = JSON.parseObject(jedis.get(token), UserDetailVO.class); Set<String> roles = new HashSet<>(); roles.add(userDetailVO.getAuthType() + "" ); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roles); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { log.info("Shiror认证" ); YtoooToken usToken = (YtoooToken) token; String sid = (String) usToken.getCredentials(); if (StringUtils.isBlank(sid)) { return null ; } log.info("sid: " + sid); return new SimpleAccount(sid, sid, "userRealm" ); } }
shiro自定义拦截器 自定义shiro拦截器来控制指定请求的访问权限,并登录shiro以便认证
我们自定义shiro拦截器主要使用其中的两个方法:
isAccessAllowed() 判断是否可以登录到系统
onAccessDenied() 当isAccessAllowed()返回false时,登录被拒绝,进入此接口进行异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Slf4j public class TokenFilter extends FormAuthenticationFilter { private String errorCode; private String errorMsg; private static JedisClusterClient jedis = JedisClusterClient.getInstance(); @Override protected boolean isAccessAllowed (ServletRequest request, ServletResponse response, Object mappedValue) { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String sid = httpServletRequest.getHeader("sid" ); if (StringUtils.isBlank(sid)) { this .errorCode = ResponseEnum.TOKEN_UNAVAILABLE.getCode(); this .errorMsg = ResponseEnum.TOKEN_UNAVAILABLE.getMessage(); return false ; } log.info("sid: " + sid); UserDetailVO userInfo = null ; try { userInfo = JSON.parseObject(jedis.get(sid), UserDetailVO.class); } catch (Exception e) { this .errorCode = ResponseEnum.TOKEN_EXPIRE.getCode(); this .errorMsg = ResponseEnum.TOKEN_EXPIRE.getMessage(); return false ; } if (userInfo == null ) { this .errorCode = ResponseEnum.TOKEN_EXPIRE.getCode(); this .errorMsg = ResponseEnum.TOKEN_EXPIRE.getMessage(); return false ; } jedis.expire(sid, 30 * 60 ); YtoooToken token = new YtoooToken(sid); getSubject(request, response).login(token); return true ; } @Override protected boolean onAccessDenied (ServletRequest request, ServletResponse response) { ResponseMessage result = Result.error(this .errorCode,this .errorMsg); String reponseJson = (new Gson()).toJson(result); response.setContentType("application/json; charset=utf-8" ); response.setCharacterEncoding("utf-8" ); ServletOutputStream outputStream = null ; try { outputStream = response.getOutputStream(); outputStream.write(reponseJson.getBytes()); } catch (IOException e) { log.error("权限校验异常" ,e); } finally { if (outputStream != null ){ try { outputStream.flush(); outputStream.close(); } catch (IOException e) { log.error("权限校验,关闭连接异常" ,e); } } } return false ; } }
配置ShiroConfig springboot中,组件通过@Bean的方式交由spring统一管理,在这里需要配置 securityManager,shiroFilter,AuthorizationAttributeSourceAdvisor
注入realm 1 2 3 4 5 6 @Bean public UserRealm userRealm () { UserRealm userRealm = new UserRealm(); return userRealm; }
注入 securityManager 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Bean("securityManager") public DefaultWebSecurityManager getManager (UserRealm realm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(realm); DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false ); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); manager.setSubjectDAO(subjectDAO); return manager; }
注入 shiroFilter 此处将自定义过滤器添加到shiro中,并配置具体哪些路径,执行shiro的那些过滤规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Bean("shiroFilter") public ShiroFilterFactoryBean factory (DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); Map<String, Filter> filterMap = new HashMap<>(); filterMap.put("token" , new TokenFilter()); factoryBean.setFilters(filterMap); factoryBean.setSecurityManager(securityManager); Map<String, String> filterRuleMap = new LinkedHashMap<>(); filterRuleMap.put("/swagger-ui.html" , "anon" ); filterRuleMap.put("/**/*.js" , "anon" ); filterRuleMap.put("/**/*.png" , "anon" ); filterRuleMap.put("/**/*.ico" , "anon" ); filterRuleMap.put("/**/*.css" , "anon" ); filterRuleMap.put("/**/ui/**" , "anon" ); filterRuleMap.put("/**/swagger-resources/**" , "anon" ); filterRuleMap.put("/**/api-docs/**" , "anon" ); filterRuleMap.put("/login/login" , "anon" ); filterRuleMap.put("/login/verifyCode" , "anon" ); filterRuleMap.put("/**" , "token" ); factoryBean.setFilterChainDefinitionMap(filterRuleMap); return factoryBean;
配置DefaultAdvisorAutoProxyCreator 解决 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。
1 2 3 4 5 6 7 8 9 10 11 @Bean public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator () { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setUsePrefix(true ); return defaultAdvisorAutoProxyCreator; }
配置 AuthorizationAttributeSourceAdvisor 使doGetAuthorizationInfo()Shiro权限配置生效 1 2 3 4 5 6 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor (DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }
在接口中控制权限 使用RequiresRoles注解来配置该接口需要的权限
当配置logical = Logical.OR时,登录这配置的权限在1,2,3中任意一个,既可以成功访问接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @ApiOperation("任务调度") @PostMapping("/dispatch") @RequiresRoles(value = { "1", "2", "3" }, logical = Logical.OR) public ResponseMessage dispatch (@RequestBody @Valid DispatchVO dispatchVO) { log.info("任务调度开始 入参:" + JSON.toJSONString(dispatchVO)); try { service.dispatch(dispatchVO); return Result.success(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getMessage()); } catch (RuntimeException e) { log.error("任务调度失败" , e); return Result.error(ResponseEnum.ERROR.getCode(), e.getMessage()); } catch (Exception e) { log.error("任务调度失败" , e); return Result.error(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getMessage()); } }
统一的异常处理 配置全局异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @ControllerAdvice @Order(value=1) public class ShiroExceptionAdvice { private static final Logger logger = LoggerFactory.getLogger(ShiroExceptionAdvice.class); @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler({AuthenticationException.class, UnknownAccountException.class, UnauthenticatedException.class, IncorrectCredentialsException.class}) @ResponseBody public ResponseMessage unauthorized (Exception exception) { logger.warn(exception.getMessage(), exception); logger.info("catch UnknownAccountException" ); return Result.error(ResponseEnum.NOT_AUTHORIZED.getCode(), ResponseEnum.NOT_AUTHORIZED.getMessage()); } @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler(UnauthorizedException.class) @ResponseBody public ResponseMessage unauthorized1 (UnauthorizedException exception) { logger.warn(exception.getMessage(), exception); return Result.error(ResponseEnum.NOT_AUTHORIZED.getCode(), ResponseEnum.NOT_AUTHORIZED.getMessage()); } }
上面使用的redis工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Bean @DependsOn("ConfigUtil") public JedisClusterClient getClient () { ml.ytooo.redis.RedisProperties.expireSeconds = redisProperties.getExpireSeconds(); ml.ytooo.redis.RedisProperties.clusterNodes = redisProperties.getClusterNodes(); ml.ytooo.redis.RedisProperties.connectionTimeout = redisProperties.getConnectionTimeout(); ml.ytooo.redis.RedisProperties.soTimeout = redisProperties.getSoTimeout(); ml.ytooo.redis.RedisProperties.maxAttempts = redisProperties.getMaxAttempts(); if (StringUtils.isNotBlank(redisProperties.password)) { ml.ytooo.redis.RedisProperties.password = redisProperties.password; }else { ml.ytooo.redis.RedisProperties.password = null ; } return JedisClusterClient.getInstance(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 @Data @Component @ConfigurationProperties(prefix = "redis.cache") public class RedisProperties { private int expireSeconds; private String clusterNodes; private int connectionTimeout; private String password; private int soTimeout; private int maxAttempts; }
依赖工具集:
1 2 3 4 5 <dependency > <groupId > ml.ytooo</groupId > <artifactId > ytooo-util</artifactId > <version > 3.7.0</version > </dependency >
收工
更多好玩好看的内容,欢迎到我的博客交流,共同进步 胡萝卜啵的博客
1 2 3 4 5 <dependency > <groupId > ml.ytooo</groupId > <artifactId > ytooo-util</artifactId > <version > 3.7.0</version > </dependency >
收工
更多好玩好看的内容,欢迎到我的博客交流,共同进步 胡萝卜啵的博客