(六)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訪問

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