(六)Spring Security基于JWT访问


我们接着上一章(五)Spring Security基于数据库的权限授权,进行开发

一:创建JWT工具

  1. 导入依赖:
    implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.0'
    
  2. 编写工具类
    @Component
    public class JwtTokenUtil implements Serializable {
        private static final long serialVersionUID = -4397248422869805076L;
    
        @Value("${jwt.secret}")
        private String secret;
    
        @Value("${jwt.expiration}")
        private long expiration;
    
        @Value("${jwt.header}")
        private String header;
    
    
        /**
         * 获取token中的信息
         * @param token 生成的token
         * @return 信息
         */
        public String getInfoFromToken(String token) {
            return getClaimFromToken(token, Claims::getSubject);
        }
    
        /**
         * 获取token的生成时间
         * @param token 生成的token
         * @return token的生成时间
         */
        public Date getIssuedAtDateFromToken(String token) {
            return getClaimFromToken(token, Claims::getIssuedAt);
        }
    
        /**
         * 获取token的过期时间
         * @param token 生成的token
         * @return token的过期时间
         */
        public Date getExpirationDateFromToken(String token) {
            return getClaimFromToken(token, Claims::getExpiration);
        }
    
        /**
         * 判断token是否过期
         * @param token 生成的token
         * @return true:过期,false:失效
         */
        private Boolean isTokenExpired(String token) {
            final Date expiration = getExpirationDateFromToken(token);
            return expiration.before(Date.from(Instant.now()));
        }
    
        public <T> T getClaimFromToken(String token, Function<Claims,T> claimsResolver){
            final Claims claims = getAllClaimsFromToken(token);
            return claimsResolver.apply(claims);
        }
    
        private Claims getAllClaimsFromToken(String token) {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }
    
    
    
        private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
            return (lastPasswordReset != null && created.before(lastPasswordReset));
        }
    
        private Boolean ignoreTokenExpiration(String token) {
            // here you specify tokens, for that the expiration is ignored
            return false;
        }
    
        /**
         * 生成令牌
         * @param userDetails
         * @return
         */
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>();
            return doGenerateToken(claims, userDetails.getUsername());
        }
    
        /**
         * 真正进行创建token的方法
         * @param claims
         * @param subject
         * @return
         */
        private String doGenerateToken(Map<String, Object> claims, String subject) {
            final Date createdDate = Date.from(Instant.now());
            final Date expirationDate = calculateExpirationDate(createdDate);
    
            return Jwts.builder()
                    .setClaims(claims) /* 自定义属性 */
                    .setSubject(subject) /* 该JWT所面向的用户 */
                    .setIssuedAt(createdDate) /* 设置发放的时间,类型为: Date*/
                    .setExpiration(expirationDate) /* 设置过期时间 类型为:Date */
                    .signWith(SignatureAlgorithm.HS512, secret) /* jwt签名算法和密钥 */
                    .compact(); /* 返回一个URL安全JWT字符串 */
        }
    
    
    
      /*  public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
            final Date created = getIssuedAtDateFromToken(token);
            return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                    && (!isTokenExpired(token) || ignoreTokenExpiration(token));
        }
    */
    
        /**
         * 刷新token
         * @param token
         * @return
         */
        public String refreshToken(String token) {
            final Date createdDate =  Date.from(Instant.now());
            final Date expirationDate = calculateExpirationDate(createdDate);
    
            final Claims claims = getAllClaimsFromToken(token);
            claims.setIssuedAt(createdDate);
            claims.setExpiration(expirationDate);
    
            return Jwts.builder()
                    .setClaims(claims)
                    .signWith(SignatureAlgorithm.HS512, secret)
                    .compact();
        }
    
        public Boolean validateToken(String token, UserDetails userDetails) {
            SecurityUser user = (SecurityUser) userDetails;
            final Date created = getIssuedAtDateFromToken(token);
           /* final Date expiration = getExpirationDateFromToken(token);
            如果token存在,且token创建日期 > 最后修改密码的日期 则代表token有效*/
            return (!isTokenExpired(token)
                    /*&& !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())*/
            );
        }
    
        /**
         * 生成过期时间
         * @param createdDate 当前时间
         * @return 返回到期时间
         */
        private Date calculateExpirationDate(Date createdDate) {
            return Date.from(Instant.ofEpochMilli(createdDate.toInstant().toEpochMilli()+expiration));
        }
    }
    

二:登录成功生成一个token给客户端

@Component
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private static final Logger log = LoggerFactory.getLogger(LoginSuccessHandler.class);

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {


        // 会帮我们跳转到上一次请求的页面上
        //super.onAuthenticationSuccess(request, response, authentication);
		
		/* 生成token */
        String token = jwtTokenUtil.generateToken((UserDetails) authentication.getPrincipal());
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        PrintWriter writer = response.getWriter();
        writer.write(token);
        writer.flush();
        writer.close();
    }
}

三:JWT 过滤器配置

@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(JwtAuthorizationTokenFilter.class);


    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Value("${jwt.header}")
    private String header;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        final String header = request.getHeader(this.header);

        String authToken = null;
        String username = null;

        if(header != null && header.startsWith("Bearer")){ /* 检测字符串是否以指定的前缀开始 */
            authToken  = header.substring(6);
            log.info("获取token:{}",authToken);
            username = jwtTokenUtil.getInfoFromToken(authToken);
        }
		/* 如果token有效,就跳过过滤器验证 */
        if(username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if(jwtTokenUtil.validateToken(authToken,userDetails)) {
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); /* 增加额外数据 */
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        /* 没有token,就继续进行过滤器验证 */
        filterChain.doFilter(request,response);
    }
}

四:WebSecurityConfig配置

http.csrf().disable(); /* CSRF禁用,因为不使用session */
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); /* 禁用session */
/* 添加过滤器 */
http.addFilterBefore(jwtAuthorizationTokenFilter,UsernamePasswordAuthenticationFilter.class);

示例
在这里插入图片描述

五:项目地址

Spring Security基于JWT访问

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章