一、介绍
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。本文是使用Shiro + JWT(Json Web Token)实现的,对于jwt部分有疑问的可以参考之前jwt相关文章,本博文中的一部分函数使用的是JWT那一篇文章文章所写的函数。
参考博文 https://blog.csdn.net/w_stronger/article/details/73109248
二、创建项目
使用idea创建空项目并添加以下依赖
1 2 3 4 5
| <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
|
三、创建自定义Realm
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 69 70 71 72
| @Service public class MyRealm extends AuthorizingRealm {
@Autowired private UserDao userDao;
@Autowired private ShiroService shiroService;
@Override public boolean supports(AuthenticationToken token) { return token instanceof JWTToken; }
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String token = (String) principals.getPrimaryPrincipal(); String username = JWTUtil.getUsername(token); User user = userDao.getUserAllInfoByTel(username); int userId = user.getId(); Set<String> permsSet = shiroService.listUserPerms(userId); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermissions(permsSet); Set<String> rolesSet = shiroService.listUserRoles(userId); info.setRoles(rolesSet);
return info; }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { String token = (String) auth.getCredentials(); String username = JWTUtil.getUsername(token); if (username == null) { throw new AuthenticationException("Username or password error"); }
User user = userDao.getUserAllInfoByTel(username); if (user == null) { throw new AuthenticationException("Username or password error"); }
if (!JWTUtil.verify(token, username, user.getPassword())) { throw new AuthenticationException("Username or password error"); }
return new SimpleAuthenticationInfo(token, token, "myRealm"); }
}
|
四、自定义JWT拦截器
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| public class JWTFilter extends BasicHttpAuthenticationFilter {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { if (this.isLoginAttempt(request, response)) { try { this.executeLogin(request, response); } catch (Exception e) { String msg = e.getMessage(); Throwable throwable = e.getCause(); if (throwable instanceof SignatureVerificationException) { msg = "Token或者密钥不正确(" + throwable.getMessage() + ")"; } else if (throwable instanceof TokenExpiredException) { } else { if (throwable != null) { msg = throwable.getMessage(); } }
return false; } } else { HttpServletRequest httpServletRequest = WebUtils.toHttp(request); String httpMethod = httpServletRequest.getMethod(); String requestURI = httpServletRequest.getRequestURI(); logger.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestURI, httpMethod); } return true; }
@Override protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { HttpServletRequest req = (HttpServletRequest) request; String authorization = req.getHeader("AuthKey"); return authorization != null; }
@Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String authorization = httpServletRequest.getHeader("AuthKey"); JWTToken token = new JWTToken(authorization); getSubject(request, response).login(token); return true; }
@Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { httpServletResponse.setStatus(HttpStatus.OK.value()); return false; } return super.preHandle(request, response); }
private void response401(ServletRequest req, ServletResponse resp) { try { HttpServletResponse httpServletResponse = (HttpServletResponse) resp; httpServletResponse.sendRedirect("/401"); } catch (IOException e) { logger.error(e.getMessage()); } } }
|
五、添加配置文件
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 69 70 71 72
| @Configuration public class ShiroConfig {
@Bean("securityManager") public DefaultWebSecurityManager getManager(MyRealm 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; }
@Bean("shiroFilter") public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager);
Map<String, Filter> filterMap = new HashMap<>(); filterMap.put("jwt", new JWTFilter()); factoryBean.setFilters(filterMap);
Map<String, String> filterRuleMap = new HashMap<>(); filterRuleMap.put("/users/login", "anon"); filterRuleMap.put("/users", "jwt"); filterRuleMap.put("/users/*", "jwt"); factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean; }
@Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; }
@Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); }
@Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
|
注:一定要将所有需要判断拦截的请求使用自定义的拦截器进行拦截
6、使用注解对接口进行权限判断以及身份验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@PostMapping @RequiresRoles("admin") @RequiresPermissions(value = "sys:user:save") public RestResult addUser(@RequestBody User user) { logger.debug("/users [Post] -> user : {}", user); try { return userService.insertUser(user); } catch (Exception e) { return RestResultFactory.restResult(402, "用户创建失败"); } }
|
7、对于常见注解的介绍
Shiro共有5个注解,接下来我们就详细说说吧
1.RequiresAuthentication:
- 使用该注解标注的类,实例,方法在访问或调用时,当前
Subject
必须在当前session中已经过认证。
2.RequiresGuest:
- 使用该注解标注的类,实例,方法在访问或调用时,当前
Subject
可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
3.RequiresPermissions:
- 当前
Subject
需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject
不具有这样的权限,则方法不会被执行。
4.RequiresRoles:
- 当前
Subject
必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当前Subject
不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException
异常。
5.RequiresUser
- 当前
Subject
必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。
使用方法:
Shiro的认证注解处理是有内定的处理顺序的,如果有个多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关):
- RequiresRoles
- RequiresPermissions
- RequiresAuthentication
- RequiresUser
- RequiresGuest
例如:你同时声明了RequiresRoles和RequiresPermissions,那就要求拥有此角色的同时还得拥有相应的权限。
1) RequiresRoles
可以用在Controller或者方法上。可以多个roles,多个roles时默认逻辑为 AND也就是所有具备所有role才能访问。
1 2 3 4 5 6
| @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresRoles { String[] value(); Logical logical() default Logical.AND; }
|
示例
1 2 3 4 5 6 7 8
| @RequiresRoles("user")
@RequiresRoles({"user","admin"})
@RequiresRoles(value={"user","admin"},logical=Logical.OR)
|
2) RequiresPermissions
1 2 3 4 5 6
| @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresPermissions { String[] value(); Logical logical() default Logical.AND; }
|
示例
1 2 3 4 5 6 7 8
| @RequiresPermissions("index:hello")
@RequiresPermissions({"index:hello","index:world"})
@RequiresPermissions(value={"index:hello","index:world"},logical=Logical.OR)
|
1) RequiresAuthentication,RequiresUser,RequiresGuest
这三个的使用方法一样
- @RequiresAuthentication
- @RequiresUser
- @RequiresGusst
总结:总体来说,shiro的存在大大的降低了关于权限部分的开发时间,使我们有更多精力关注业务开发,也让我们能快速的开发出一个完善的权限系统,避免自己开发权限时出现的权限考虑不周到的情况