• 【SpringSecurity】十一、SpringSecurity集成JWT实现token的方法与校验


    1、依赖与配置

    添加JWT的maven依赖:

    
    <dependency>
        <groupId>com.auth0groupId>
        <artifactId>java-jwtartifactId>
        <version>3.11.0version>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    application.yaml中配置密钥的值,方便代码中引用和后续更改:

    jwt:
      secretKey: mykey
    
    
    • 1
    • 2
    • 3

    2、JWT工具类

    这里的命名改为JWTService好点,Utils命名似乎偏静态方法一点。

    @Component
    @Slf4j
    public class JwtUtils {
        //算法密钥
        @Value("${jwt.secretKey}")
        private String jwtSecretKey;
    
        /**
         * 创建jwt
         *
         * @param userInfo 用户信息
         * @param authList 用户权限列表
         * 根据登录用户的数据库信息和权限信息,加上服务端密钥,创建token
         * @return 返回jwt(JSON WEB TOKEN)
         */
        public String createToken(String userInfo, List<String> authList) {
            //创建时间
            Date currentTime = new Date();
            //过期时间,5分钟后过期
            Date expireTime = new Date(currentTime.getTime() + (1000 * 60 * 5));
            //jwt的header信息
            Map<String, Object> headerClaims = new HashMap<>();
            headerClaims.put("type", "JWT");
            headerClaims.put("alg", "HS256");
            //创建jwt
            return JWT.create()
                    .withHeader(headerClaims) // 头部信息
                    .withIssuedAt(currentTime) //已注册声明:签发日期,发行日期
                    .withExpiresAt(expireTime) //已注册声明 过期时间
                    .withIssuer("llg")  //已注册声明,签发人
                    .withClaim("userInfo", userInfo) //私有声明,可以自己定义
                    .withClaim("authList", authList) //私有声明,可以自定义
                    .sign(Algorithm.HMAC256(jwtSecretKey)); // 签名,使用HS256算法签名,并使用密钥
            //HS256是一种对称算法,这意味着只有一个密钥,在双方之间共享。 使用相同的密钥生成签名并对其进行验证。 应特别注意钥匙是否保密。
        }
    
        /**
         * 验证jwt的签名,简称验签
         * @param token 需要验签的jwt
         * @return 验签结果
         */
        public boolean verifyToken(String token) {
            //获取验签类对象
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
            try {
                //验签,如果不报错,则说明jwt是合法的,而且也没有过期
                DecodedJWT decodedJWT = jwtVerifier.verify(token);
                return true;
            } catch (JWTVerificationException e) {
                //如果报错说明jwt 为非法的,或者已过期(已过期也属于非法的)
                log.error("验签失败:{}", token);
                e.printStackTrace();
            }
            return false;
        }
    
        /**
         * 从token中获取用户信息
         * 这个userInfo是创建token时我自己塞进去的
         * @param token jwt
         * @return 用户信息
         */
        public String getUserInfo(String token) {
            //创建jwt验签对象
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
            try {
                //验签(主要为了同时的获取解析的结果)
                DecodedJWT decodedJWT = jwtVerifier.verify(token);
                //获取payload中userInfo的值,并返回
                return decodedJWT.getClaim("userInfo").asString();
            } catch (JWTVerificationException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 获取用户权限
         *
         * @param token
         * @return
         */
        public List<String> getUserAuth(String token) {
            //创建jwt验签对象
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
            try {
                //验签(主要为了同时的获取解析的结果)
                DecodedJWT decodedJWT = jwtVerifier.verify(token);
                //获取payload中的自定义数据authList(权限列表),并返回
                return decodedJWT.getClaim("authList").asList(String.class);
            } catch (JWTVerificationException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    }
    
    
    • 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

    再贴一下下统一结果类的定义:

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class HttpResult implements Serializable {
        private Integer code; //响应码
        private String msg; //响应消息
        private Object data; //响应对象
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    下面是安全用户类,用于在数据库的用户对象类SysUser和返给框架的官方对象类UserDetails之间做过渡转换。UserDetails <====> SecurityUser <====> SysUser

    @Setter
    public class SecurityUser implements UserDetails {
    
        private  final SysUser sysUser;
    
    	private List<SimpleGrantedAuthority> simpleGrantedAuthorities;
    
        public SecurityUser(SysUser sysUser) {
            this.sysUser=sysUser;
        }
    	public SysUser getSysUser() {
        return sysUser;
    }
    
    
        @Override
    	public Collection<? extends GrantedAuthority> getAuthorities() {
        	return simpleGrantedAuthorities;
    	}
    
    
    
        @Override
        public String getPassword() {
            String userPassword=this.sysUser.getPassword();
    		//注意清除密码
    		this.sysUser.setPassword(null);
    		return userPassword;
    
        }
    
        @Override
        public String getUsername() {
            return sysUser.getUsername();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return sysUser.getAccountNoExpired().equals(1);
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return sysUser.getAccountNoLocked().equals(1);
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return sysUser.getCredentialsNoExpired().equals(1);
        }
    
        @Override
        public boolean isEnabled() {
            return sysUser.getEnabled().equals(1);
        }
    }
    
    
    
    • 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

    3、认证成功处理器

    自定义处理器,实现AuthenticationSuccessHandler,当用户登录认证成功后,会执行这个处理器,即认证成功处理器

    /**
     * 认证成功处理器,当用户登录成功后,会执行此处理器
     */
    @Component
    public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        //使用此工具类进行序列化
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private JwtUtils jwtUtils;
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            //从认证对象中获取认证用户信息
    		//查看前面第六章的UserDetailsService接口的loadUserByUsername方法,
    		//返回给框架的是一个自定义的SecurityUser对象(Security实现了UserDetails)
            SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
            //从SecurityUser中拿出和底层MYSQL挂钩的SysUser类信息
            String userInfo=objectMapper.writeValueAsString(securityUser.getSysUser());
            List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) securityUser.getAuthorities();
            //List转List
            List<String> authList=new ArrayList<>();
            for (SimpleGrantedAuthority authority : authorities) {
                authList.add(authority.getAuthority());
            }
    		//也可使用stream流代替上面的for循环
    		List<String> authList = authorities.stream().map(
    		        a -> {
    		           return a.getAuthority();
    		        }
    		).collect(Collectors.toList());
    
    		//也可使用stream流+Lambda表达式
    		List<String> authList = authorities.stream()
    		.map(SimpleGrantedAuthority::getAuthority)
    		.collect(Collectors.toList());
    
    
            // 调用前面的JWT工具类方法创建jwt
            String token = jwtUtils.createToken(userInfo,authList);
    
            //返回给前端token@Builder模式创建对象
            HttpResult httpResult = HttpResult.builder().code(200).msg("OK").data(token).build();
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(objectMapper.writeValueAsString(httpResult));
            writer.flush();
        }
    }
    
    
    
    • 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

    4、创建JWT过滤器

    定义JWT过滤器,用来检验一个个对接口的请求中token是否合法,注意放行登录接口:

    /**
     * 定义一次性请求过滤器
     */
    @Component
    @Slf4j
    public class JwtCheckFilter extends OncePerRequestFilter {
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private JwtUtils jwtUtils;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            //获取请求uri
            String requestURI = request.getRequestURI();
            // 如果是登录页面,放行
            if (requestURI.equals("/login")) {
                filterChain.doFilter(request, response);
                return;
            }
            //获取请求头中的Authorization,前端一般这么传,key为Authorization
            String authorization = request.getHeader("Authorization");
            //如果Authorization为空,那么不允许用户访问,直接返回
            if (!StringUtils.hasText(authorization)) {
                printFront(response, "没有登录!");
                return;
            }
            //Authorization 去掉头部的Bearer 信息,获取token值
            String jwtToken = authorization.replace("Bearer ", "");
            //验签
            boolean verifyTokenResult = jwtUtils.verifyToken(jwtToken);
            //验签不成功
            if (!verifyTokenResult) {
                printFront(response, "jwtToken 已过期");
                return;
            }
    		//到这儿算是验证通过,但还没结束,还要将信息填充后返给SpringSecurity框架
            //从payload中获取userInfo
            String userInfo = jwtUtils.getUserInfo(jwtToken);
            //从payload中获取授权列表
            List<String> userAuth = jwtUtils.getUserAuth(jwtToken);
            //在认证成功处理器中,创建token时,userInfo里放的是SysUser对象的序列化字符串,这里反序列化
            SysUser sysUser = objectMapper.readValue(userInfo, SysUser.class);
            SecurityUser securityUser = new SecurityUser(sysUser);
            //设置权限
            List<SimpleGrantedAuthority> authList = userAuth.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
            securityUser.setAuthorityList(authList);
    
    		//填充信息
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToke = new UsernamePasswordAuthenticationToken(securityUser
                    , null, authList);
            //通过安全上下文设置认证信息
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToke);
            //继续访问相应的rul等
            filterChain.doFilter(request, response);
    
        }
        
        /**
     	* 定义一个通过response向前端返回数据的方法
     	* 这里不是controller层,不是你直接返回个结果类就行的,注意区别
     	*/
        private void printFront(HttpServletResponse response, String message) throws IOException {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=utf-8");
            PrintWriter writer = response.getWriter();
            HttpResult httpResult = new HttpResult();
            httpResult.setCode(401);
            httpResult.setMsg(message);
    
            writer.print(objectMapper.writeValueAsString(httpResult));
            writer.flush();
        }
    }
    
    
    
    • 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

    上面的过滤器中,除了正常的验签,最后的消息填充与保存在安全上下文,就是下图中的第十步:

    在这里插入图片描述

    5、安全配置类

    修改下安全配置类,把上面的处理器和过滤器加进来。

    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Resource
        private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
        @Resource
        private JwtCheckFilter jwtCheckFilter;
        @Override
        protected void configure(HttpSecurity http) throws Exception {
        	//先看token是否合法,再走框架的用户名密码校验过滤器
            http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);
            //要是有之间的验证码校验,则它应该在token校验之前
            //认证通过后,走认证成功处理器,颁发token
            http.formLogin().successHandler(myAuthenticationSuccessHandler).permitAll();
            //简单按接口加个权限要求
    		http.authorizeRequests()
            .mvcMatchers("/student/**")
            .hasAnyAuthority("student:query","student:update")
    		.anyRequest()
    		.authenticated(); //任何请求均需要认证(登录成功)才能访问
            http.csrf().disable();  //跨域
            //禁用session方式
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    
       
    }
    
    
    • 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

    效果:
    在这里插入图片描述

    登录认证后返给前端token:

    在这里插入图片描述

  • 相关阅读:
    什么是生物识别技术?它是如何用于安全领域的?
    umi乌米框架下使用react-dev-inspector ,通过点击页面跳转到vscode对应代码行
    佳作导读 | 《C++ Core Guidelines》
    北美干旱监测 (NADM)数据集
    FL Studio 21.2.3.3586 for Mac中文版新功能介绍及2024年最新更新日志
    穿越火线几次体验良好的游戏优化方案
    【自然资源】全域国土综合整治相关问题
    多线程与高并发(二)—— Synchronized 加锁解锁流程
    前端技术知识(含八股)总结 - 持续更新中
    从零开始搭建一个组件库(二)
  • 原文地址:https://blog.csdn.net/llg___/article/details/132629814