此篇基于 SpringBoot 整合 Shiro & Jwt 进行鉴权 相关代码编写与解析

首先我们创建 JwtFilter 类 继承自 BasicHttpAuthenticationFilter

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

此类是个过滤器,后期配置Shiro时会引用到

重写4个重要的方法 其执行顺序亦是如下

  1. 1. preHandle(..) 我理解为是前置处理
  2. 2. isAccessAllowed(..) 请求是否被允许
  3. 3. isLoginAttempt(..) 是否是尝试登陆,去查实[请求头]里是否包含[Authorization]
  4. 4. executeLogin(..) 执行登陆操作 其会调用getSubject(request, response).login(jwtToken)进行登陆验权

preHandle 方法

可以理解为前置处理,我们在这进行一些跨域的必要设置

  1. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  2. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  3. httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
  4. httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
  5. httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
  6. //跨域请求会发送两次请求首次为预检请求,其请求方法为 OPTIONS
  7. if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
  8. httpServletResponse.setStatus(HttpStatus.OK.value());
  9. return false;
  10. }
  11. return super.preHandle(request, response);

isAccessAllowed 方法

其实这个方法我们会手动调用 isLoginAttempt 方法及 executeLogin 方法

isLoginAttempt判断用户是否想尝试登陆,判断依据为请求头中是否包含 Authorization 授权信息,也就是 Token 令牌

如果有则再执行executeLogin方法进行登陆验证操作,就是我们整合后的鉴权操作,因为用Token抛开了Session,此处就相当于是否存在Session的操作,存在则表明登陆成功,不存在则需要登陆操作,或者Session过期需要重新登陆是一个原理性质,此方法在这里是验证JwtToken是否合法,不合法则返回401需要重新登陆

不合法的原因大致一这些

  • Token不正确 可以是被篡改 不能解析
  • Token过期
  • Token已被注销(需自己去实现)
  1. @Override
  2. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
  3. if (this.isLoginAttempt(request, response)) {
  4. // 进行验证登陆JWT 可以trycatch下可以捕获Token过期异常等信息
  5. this.executeLogin(request, response);
  6. } else {
  7. // 没有携带Token
  8. HttpServletRequest httpRequest = (HttpServletRequest)request;
  9. String httpMethod = httpRequest.getMethod();
  10. String requestURI = httpRequest.getRequestURI();
  11. logger.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestURI, httpMethod);
  12. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  13. httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
  14. httpServletResponse.setCharacterEncoding("UTF-8");
  15. httpServletResponse.setContentType("application/json; charset=utf-8");
  16. try (PrintWriter out = httpServletResponse.getWriter()) {
  17. out.append("用户认证失败" + msg);
  18. } catch (IOException e) {
  19. //logger.error("直接返回Response信息出现IOException异常", e);
  20. }
  21. return false;
  22. }
  23. return true;
  24. }

isLoginAttempt 方法 是否尝试登陆

  1. @Override
  2. protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
  3. String token = this.getAuthzHeader(request);
  4. return token != null;
  5. }

executeLogin 方法

执行登陆操作 其实就是对 Token 进行验证操作,这里我们需要另外一个类去处理 Token 验证 (MyRealm 类的doGetAuthenticationInfo方法)

  1. @Override
  2. protected boolean executeLogin(ServletRequest request, ServletResponse response) {
  3. String token = this.getAuthzHeader(request);
  4. //这里需要自己实现对Token验证操作
  5. JwtToken jwtToken = new JwtToken(token);
  6. getSubject(request, response).login(jwtToken);//如果登陆失败会抛出异常(Token鉴权失败)
  7. return true;
  8. }

创建 JwtToken 类 继承自AuthenticationToken

org.apache.shiro.authc.AuthenticationToken

这里本来是存取用户名及密码的字段用来登陆,现在因为是Token不存在需要携带用户名 所以把字段都设置成Token

  1. public class JwtToken implements AuthenticationToken {
  2. private static final long serialVersionUID = -634556778977L;
  3. private String token;
  4. public JwtToken(String token) {
  5. this.token = token;
  6. }
  7. @Override
  8. public Object getPrincipal() {
  9. return token;
  10. }
  11. @Override
  12. public Object getCredentials() {
  13. return token;
  14. }
  15. }

JwtConfig类的创建

这个类用于创建 Token 及解码 Token 里的信息

  1. @ConfigurationProperties(prefix = "config.jwt")
  2. @Component
  3. public class JwtConfig {
  4. private String secret;
  5. private long expire;
  6. private String header;
  7. /**
  8. * 生成token
  9. *
  10. * @param subject
  11. * @return
  12. */
  13. public String createToken(String subject) {
  14. Date nowDate = new Date();
  15. //过期时间 默认单位 (天)
  16. Date expireDate = new Date(nowDate.getTime() + expire * 1000 * 60 * 60 * 24);
  17. return Jwts.builder()
  18. .setHeaderParam("typ", "JWT")
  19. .setSubject(subject)
  20. .setIssuedAt(nowDate)
  21. .setExpiration(expireDate)
  22. .signWith(SignatureAlgorithm.HS512, secret)
  23. .compact();
  24. }
  25. /**
  26. * 获取token中注册信息
  27. *
  28. * @param token
  29. * @return
  30. */
  31. public Claims getTokenClaim(String token) {
  32. try {
  33. return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
  34. } catch (Exception e) {
  35. return null;
  36. }
  37. }
  38. /**
  39. * 获取token 解析信息
  40. * @param token
  41. * @return
  42. */
  43. public String getTokenInfo(String token){
  44. Claims claims=getTokenClaim(token);
  45. if(claims==null){
  46. throw new RuntimeException("Token 信息异常");
  47. }
  48. String tokenInfo=claims.getSubject();
  49. if(StringUtils.isBlank(tokenInfo)){
  50. throw new RuntimeException("Token 信息异常 解析值为空");
  51. }
  52. return tokenInfo;
  53. }
  54. /**
  55. * 验证token是否过期失效
  56. *
  57. * @param expirationTime
  58. * @return
  59. */
  60. public boolean isTokenExpired(Date expirationTime) {
  61. return expirationTime.before(new Date());
  62. }
  63. /**
  64. * 获取token失效时间
  65. *
  66. * @param token
  67. * @return
  68. */
  69. public Date getExpirationDateFromToken(String token) {
  70. return getTokenClaim(token).getExpiration();
  71. }
  72. /**
  73. * 获取用户名从token中
  74. */
  75. public String getUsernameFromToken(String token) {
  76. return getTokenClaim(token).getSubject();
  77. }
  78. /**
  79. * 获取jwt发布时间
  80. */
  81. public Date getIssuedAtDateFromToken(String token) {
  82. return getTokenClaim(token).getIssuedAt();
  83. }
  84. ...set get
  85. }

配置文件信息

  1. config.jwt.secret=123!@#
  2. config.jwt.expire=15
  3. config.jwt.header=Authorization

实现自己的Realm 创建MyRealm类 继承自AuthorizingRealm

  1. import io.jsonwebtoken.Claims;
  2. import org.apache.logging.log4j.LogManager;
  3. import org.apache.logging.log4j.Logger;
  4. import org.apache.shiro.authc.AuthenticationException;
  5. import org.apache.shiro.authc.AuthenticationInfo;
  6. import org.apache.shiro.authc.AuthenticationToken;
  7. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  8. import org.apache.shiro.authz.AuthorizationInfo;
  9. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  10. import org.apache.shiro.realm.AuthorizingRealm;
  11. import org.apache.shiro.subject.PrincipalCollection;
  12. import org.springframework.stereotype.Component;
  13. import javax.annotation.Resource;
  14. import java.util.HashSet;
  15. import java.util.List;
  16. import java.util.Set;
  17. import java.util.stream.Collectors;
  18. /**
  19. * @author zy
  20. */
  21. @Component
  22. public class MyRealm extends AuthorizingRealm {
  23. private static Logger logger = LogManager.getLogger(MyRealm.class);
  24. /**
  25. * 这里需要实现自己的用户登陆验证信息及 数据权限相关的信息获取
  26. */
  27. @Resource
  28. private UserService userService;
  29. @Resource
  30. private JwtConfig jwtConfig;
  31. @Override
  32. public boolean supports(AuthenticationToken token) {
  33. return token instanceof JwtToken;
  34. }
  35. /**
  36. * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
  37. */
  38. @Override
  39. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  40. logger.info("====================数据权限认证====================");
  41. String username = jwtConfig.getTokenInfo(principals.toString());
  42. UserInfo user = userService.getUserAndRole(username);
  43. SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
  44. List<Role> roles = user.getRoles();
  45. if (roles.isEmpty()) {
  46. logger.warn("该用户 {} 没有角色,默认赋予user角色", username);
  47. Role r = new Role();
  48. r.setRole("user");
  49. roles.add(r);
  50. }
  51. /**
  52. * 这里是我自己实现的数据权限认证可做参考用
  53. */
  54. Set<String> permissionSet = new HashSet<>(roles.size() * 16);
  55. for (Role role : roles) {
  56. List<Permission> temList = role.getPermissions();
  57. if (temList == null || temList.isEmpty()) {
  58. logger.warn("该角色 {} 没有赋予相应权限信息", role.getRole());
  59. continue;
  60. }
  61. for (Permission tem : temList) {
  62. if (tem.getId().indexOf(":") > -1) {
  63. permissionSet.add(tem.getId());
  64. }
  65. }
  66. }
  67. simpleAuthorizationInfo.setRoles(roles.stream().map(Role::getRole).collect(Collectors.toSet()));
  68. simpleAuthorizationInfo.setStringPermissions(permissionSet);
  69. return simpleAuthorizationInfo;
  70. }
  71. /**
  72. * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
  73. */
  74. @Override
  75. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) {
  76. logger.info("====================Token认证====================");
  77. String token = auth.getCredentials().toString();
  78. Claims claims = jwtConfig.getTokenClaim(token);
  79. if (claims == null) {
  80. throw new AuthenticationException("解析Token异常");
  81. }
  82. if (jwtConfig.isTokenExpired(claims.getExpiration())) {
  83. throw new AuthenticationException("Token过期");
  84. }
  85. String username = claims.getSubject();
  86. if (username == null || username == "") {
  87. logger.error("Token中帐号为空");
  88. throw new AuthenticationException("Token中帐号为空");
  89. }
  90. UserInfo user = userService.getUserByName(username);
  91. if (user == null) {
  92. throw new AuthenticationException("该帐号不存在");
  93. }
  94. DataContextHolder.setCurrentUser(user);
  95. return new SimpleAuthenticationInfo(token, token, getName());
  96. }
  97. }

配置Shiro 创建ShiroConfig类

LifecycleBeanPostProcessor这个类并不一定要手动创建,手动创建可能存在一些问题。我遇见的坑就在这里。至于原因希望大家不吝赐教

  1. import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
  2. import org.apache.shiro.mgt.DefaultSubjectDAO;
  3. import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  4. import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  5. import org.apache.shiro.web.filter.authc.AnonymousFilter;
  6. import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.data.redis.core.RedisTemplate;
  10. import javax.servlet.Filter;
  11. import java.util.HashMap;
  12. import java.util.LinkedHashMap;
  13. import java.util.Map;
  14. /**
  15. * @author zy
  16. */
  17. @Configuration
  18. public class ShiroConfig {
  19. @Bean("securityManager")
  20. public DefaultWebSecurityManager getManager(MyRealm myRealm) {
  21. DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
  22. manager.setRealm(myRealm);
  23. // 关闭Shiro自带的session
  24. DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
  25. DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
  26. defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
  27. subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
  28. manager.setSubjectDAO(subjectDAO);
  29. // 设置自定义Cache缓存 根据项目情况而设置
  30. manager.setCacheManager(new CustomCacheManager());
  31. return manager;
  32. }
  33. /**
  34. * 添加自己的过滤器,自定义url规则
  35. */
  36. @Bean("shiroFilter")
  37. public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, RedisTemplate<String, String> redisTemplate, JwtConfig jwtConfig) {
  38. ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
  39. //配置过滤器 对 『anon』不进行拦截
  40. Map<String, Filter> filterMap = new HashMap<>(3);
  41. filterMap.put("anon", new AnonymousFilter());
  42. filterMap.put("jwt", new JwtFilter(redisTemplate, jwtConfig));
  43. factoryBean.setFilters(filterMap);
  44. factoryBean.setSecurityManager(securityManager);
  45. factoryBean.setLoginUrl("/login");
  46. LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(16);
  47. // 配置不过滤
  48. filterChainDefinitionMap.put("/login", "anon");
  49. filterChainDefinitionMap.put("/testpage", "anon");
  50. filterChainDefinitionMap.put("/static/**", "anon");
  51. filterChainDefinitionMap.put("/login/**", "anon");
  52. filterChainDefinitionMap.put("/unauthorized/**", "anon");
  53. // swagger
  54. filterChainDefinitionMap.put("/swagger**/**", "anon");
  55. filterChainDefinitionMap.put("/v2/**", "anon");
  56. filterChainDefinitionMap.put("/webjars/**", "anon");
  57. filterChainDefinitionMap.put("/**", "jwt");
  58. factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
  59. return factoryBean;
  60. }
  61. // @Bean
  62. // public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
  63. // return new LifecycleBeanPostProcessor();
  64. // }
  65. @Bean
  66. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
  67. AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
  68. advisor.setSecurityManager(securityManager);
  69. return advisor;
  70. }
  71. }

Shiro&Jwt验证的更多相关文章

  1. 踩坑之路---JWT验证

    使用JWT验证客户的携带的token 客户端在请求接口时,需要在request的head中携带一个token令牌 服务器拿到这个token解析获取用户资源,这里的资源是非重要的用户信息 目前我的理解, ...

  2. SpringBoot2.0+Shiro+JWT 整合

    SpringBoot2.0+Shiro+JWT 整合 JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息. 我们利用一定的编 ...

  3. 基于Shiro,JWT实现微信小程序登录完整例子

    小程序官方流程图如下,官方地址 : https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html ...

  4. spring-boot-plus集成Shiro+JWT权限管理

    SpringBoot+Shiro+JWT权限管理 Shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 使用Shiro的易于理解的API,您可以 ...

  5. 使用Shiro+JWT完成的微信小程序的登录(含讲解)

    使用Shiro+JWT完成的微信小程序的登录 源码地址https://github.com/Jirath-Liu/shiro-jwt-wx 微信小程序用户登陆,完整流程可参考下面官方地址,本例中是按此 ...

  6. Shiro (Shiro + JWT + SpringBoot应用)

    Shiro (Shiro + JWT + SpringBoot应用) 目录 Shiro (Shiro + JWT + SpringBoot应用) 1.Shiro的简介 2.Shiro + JWT + ...

  7. SpringMVC+Apache Shiro+JPA(hibernate)案例教学(三)给Shiro登录验证加上验证码

    序: 给Shiro加入验证码,有多种方式,当然你也可以通过继承修改FormAuthenticationFilter类,通过Shiro去验证验证码.具体实现请百度: 应用Shiro到Web Applic ...

  8. golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息

    golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放 ...

  9. shiro 身份验证

    shiro身份验证: 参考链接:http://jinnianshilongnian.iteye.com/blog/2019547 即在应用中证明是本人进行操作,一般通过用户名来证明 在shiro中,用 ...

随机推荐

  1. 微信小程序中的左右联动

    微信小程序端的左右联动-滚动效果插件: 效果图如下:                                                                          ...

  2. Importing data in R 1

    目录 Importing data in R 学习笔记1 flat files:CSV txt文件 packages:readr read_csv() read_tsv read_delim() da ...

  3. Redis事务实现原理

    一:简介 Redis事务通常会使用MULTI,EXEC,WATCH等命令来完成,redis实现事务实现的机制与常见的关系型数据库有很大的却别,比如redis的事务不支持回滚,事务执行时会阻塞其它客户端 ...

  4. 17个IoC 软件包和项目

    原文:17个IoC 软件包和项目 1.Autofac GitHub:https://github.com/autofac/Autofac 描述:An addictive .NET IoC contai ...

  5. 用户登录(php)

    <!DOCTYPE HTML><html><head><meta charset="utf-8"><script type=& ...

  6. 【Python】循环控制保留字

  7. Visibility Graph Analysis of Geophysical Time Series: Potentials and Possible Pitfalls

    Tasks: invest papers  3 篇. 研究主动权在我手里.  I have to.  1. the benefit of complex network: complex networ ...

  8. ORA-00904: "I_LEVEL": invalid identifier

    问题描述 ORA-00904: "I_LEVEL": invalid identifier 标示符无效

  9. 前后端交互技术之servlet与form表单提交请求及ajax提交请求

    1.先来个简单的form表单 login.jsp,建在webcontent目录下(url写相对路径就可以了) <!DOCTYPE html><html><head> ...

  10. 大数据-Storm

    Storm 流式处理框架 Storm是实时的,分布式,高容错的计算系统.java+cljoure Storm常驻内存,数据在内存中处理不经过磁盘,数据通过网络传输. 底层java+cljoure构成, ...