springboot 配置 jjwt

JWT 全名 JSON WEB Token 主要作用爲用戶身份驗證, 廣泛應用與前後端分離項目當中.

    JWT 的優缺點 : https://www.jianshu.com/p/af8360b83a9f

 

一、pom.xml 引入jar文件

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>

二、添加jwt自定義字段 application.yml

# jwt 配置
custom:
  jwt:
    # header:憑證(校驗的變量名)
    header: token
    # 有效期1天(單位:s)
    expire: 5184000
    # secret: 祕鑰(普通字符串)
    secret: aHR0cHM6Ly9teS5vc2NoaW5hLm5ldC91LzM2ODE4Njg=
    # 簽發者
    issuer: test-kou

三、添加加密解密方法


import io.jsonwebtoken.*;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT 工具類
 * <p>
 * jwt含有三部分:頭部(header)、載荷(payload)、簽證(signature)
 * (1)頭部一般有兩部分信息:聲明類型、聲明加密的算法(通常使用HMAC SHA256)
 * (2)載荷該部分一般存放一些有效的信息。jwt的標準定義包含五個字段:
 * - iss:該JWT的簽發者
 * - sub: 該JWT所面向的用戶
 * - aud: 接收該JWT的一方
 * - exp(expires): 什麼時候過期,這裏是一個Unix時間戳
 * - iat(issued at): 在什麼時候簽發的
 * (3)簽證(signature) JWT最後一個部分。該部分是使用了HS256加密後的數據;包含三個部分
 *
 * @author kou
 */
@ConfigurationProperties(prefix = "custom.jwt")
@Data
@Component
public class JwtUtil {

    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);

    // 祕鑰
    private String secret;

    // 有效時間
    private Long expire;

    // 用戶憑證
    private String header;

    // 簽發者
    private String issuer;

    /**
     * 生成token簽名
     *
     * @param subject
     * @return
     */
    public String generateToken(String subject) {

        final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        Date now = new Date();
        // 過期時間
        Date expireDate = new Date(now.getTime() + expire * 1000);

        //Create the Signature SecretKey
        final byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(Base64.getEncoder().encodeToString(getSecret().getBytes()));
        final Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        final Map<String, Object> headerMap = new HashMap<>();
        headerMap.put("alg", "HS256");
        headerMap.put("typ", "JWT");

        //add JWT Parameters
        final JwtBuilder builder = Jwts.builder()
                .setHeaderParams(headerMap)
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expireDate)
                .setIssuer(getIssuer())
                .signWith(signatureAlgorithm, signingKey);

        logger.info("JWT[" + builder.compact() + "]");
        return builder.compact();

    }

    /**
     * 解析token
     *
     * @param token token
     * @return
     */
    public Claims parseToken(String token) {

        Claims claims = null;
        try {
            final byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(Base64.getEncoder().encodeToString(getSecret().getBytes()));
            claims = Jwts.parser().setSigningKey(apiKeySecretBytes).parseClaimsJws(token).getBody();
            logger.info("Parse JWT token by: ID: {}, Subject: {}, Issuer: {}, Expiration: {}", claims.getId(), claims.getSubject(), claims.getIssuer(), claims.getExpiration());
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException
                | IllegalArgumentException e) {
            logger.info("Parse JWT errror " + e.getMessage());
            return null;
        }
        return claims;
    }

    /**
     * 判斷token是否過期
     *
     * @param expiration
     * @return
     */
    public boolean isExpired(Date expiration) {

        return expiration.before(new Date());
    }

}

 

四、添加攔截方法,攔截token處理,我這邊使用的是filter攔截處理,根據自己需求自定


import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * 如果請求中(請求頭或者Cookie)中存在JWT,則:
 * 1、解析JWT並查找對應的用戶信息,然後加入request attribute中
 * 2、更新Cookie時間、更新JWT失效時間放入Header
 * <p>
 * OncePerRequestFilter 一次請求只進入一次filter
 */
@Component
@Slf4j
public class JWTFilter extends OncePerRequestFilter {

    public static final String SECURITY_USER = "SECURITY_USER";

    // 設置不需要校驗的路徑
    private static final String[] NOT_CHECK_URL = {"/login", "/registered"};

    @Autowired
    private JwtUtil jwtUtil;

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

        // 判斷是否需要對token處理
        if (!isNotCheck(request.getRequestURI())) {
            // 獲取token
            String token = getToken(request);

            if (StringUtils.isBlank(token)) {
                log.info("請求無效,原因:{} 爲空!", jwtUtil.getHeader());
                request.setAttribute("exceptionCode", SystemParameters.TOKEN_IS_NULL.getIndex());
                request.setAttribute("exceptionMessage", "請求無效,原因:" + jwtUtil.getHeader() + " 爲空!");
                request.getRequestDispatcher("/exception/authentication").forward(request, response);
                // throw new AuthenticationException(SystemParameters.TOKEN_IS_NULL.getIndex(),  "請求無效,原因:" + jwtUtil.getHeader() + " 爲空!");
                return;
            }

            Claims claims = jwtUtil.parseToken(token);

            // 判斷簽名信息
            if (null != claims && !claims.isEmpty() && !jwtUtil.isExpired(claims.getExpiration())) {
                // 獲取簽名用戶信息
                String userId = claims.getSubject();
                //獲取相應的用戶信息,可以在過濾器中先行獲取,也可以先保存用戶ID,在需要時進行獲取
                // User user = usersService.findById(Long.valueOf(userId));

                request.setAttribute(SECURITY_USER, userId);
                Cookie jwtCookie = new Cookie(jwtUtil.getHeader(), URLEncoder.encode(token, "UTF-8"));
                jwtCookie.setHttpOnly(true);
                jwtCookie.setPath("/");
                response.addCookie(jwtCookie);

                response.addHeader(jwtUtil.getHeader(), token);
            } else {
                log.info("{} 無效,請重新登錄!", jwtUtil.getHeader());
                request.setAttribute("exceptionCode", SystemParameters.AUTHENTICATION_FAILED.getIndex());
                request.setAttribute("exceptionMessage", jwtUtil.getHeader() + " 無效,請重新登錄!");
                request.getRequestDispatcher("/exception/authentication").forward(request, response);
                // throw new AuthenticationException(SystemParameters.AUTHENTICATION_FAILED.getIndex(), jwtUtil.getHeader() + " 無效,請重新登錄!");
                return;
            }
        }

        filterChain.doFilter(request, response);
    }

    /**
     * 獲取token
     *
     * @param request
     * @return 返回token
     */
    private String getToken(HttpServletRequest request) {

        //先從header中獲取token
        String token = request.getHeader(jwtUtil.getHeader());

        //再從cookie中獲取
        if (StringUtils.isBlank(token)) {
            try {
                Cookie[] cookies = request.getCookies();
                if (cookies != null) {
                    for (Cookie cookie : cookies) {
                        if (jwtUtil.getHeader().equals(cookie.getName())) {
                            token = URLDecoder.decode(cookie.getValue(), "UTF-8");
                        }
                    }
                }
            } catch (Exception e) {
                log.error("Can NOT get jwt from cookie -> Message: {}", e);
            }
        }

        return token;
    }

    /**
     * 根據url判斷是否需要校驗,false需要校驗
     *
     * @param url
     * @return 是否需要校驗
     */
    private boolean isNotCheck(String url) {

        // 處理路徑以"/" 結尾的"/"
        url = url.endsWith("/") ? url.substring(0, url.lastIndexOf("/")) : url;

        for (String path : NOT_CHECK_URL) {
            // 判斷是否以 "/**" 結尾
            if (path.endsWith("/**")) {
                return url.startsWith(path.substring(0, path.lastIndexOf("/") + 1))
                        || url.equals(path.substring(0, path.lastIndexOf("/")));
            }

            // 判斷url == path
            if (url.equals(path)) {
                return true;
            }
        }

        return false;
    }

}

 

五、配置過濾器


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 過濾器配置
 *
 * @author kou
 */
@Configuration
public class FilterConfig {

    @Autowired
    private JWTFilter jwtFilter;

    @Bean
    public FilterRegistrationBean registrationBean() {

        FilterRegistrationBean registration = new FilterRegistrationBean();
        // 設置過濾器
        registration.setFilter(jwtFilter);
        // 設置攔截路徑
        registration.addUrlPatterns("/rest/*");
        // 設置過濾器名稱
        registration.setName("JWTFilter");
        // 設置過濾器執行順序
        registration.setOrder(1);

        return registration;
    }
}

六、添加異常處理類


/**
 * 認證異常類
 *
 * @author kou
 */
public class AuthenticationException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    //自定義錯誤碼
    private Integer code;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    /**
     * Creates a new AuthenticationException.
     */
    public AuthenticationException() {
        super();
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     */
    public AuthenticationException(String message) {
        super(message);
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param cause the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(Throwable cause) {
        super(cause);
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     * @param cause   the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(String message, Throwable cause) {
        super(message, cause);
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     */
    public AuthenticationException(int code, String message) {
        super(message);
        this.code = code;
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param cause the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(int code, Throwable cause) {
        super(cause);
        this.code = code;
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     * @param cause   the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

}

七、添加全局異常處理類


import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 全局異常處理
 *
 * @author kou
 */
@ControllerAdvice
public class GlobalException {

    /**
     * 授權登錄異常
     */
    @ResponseBody
    @ExceptionHandler(AuthenticationException.class)
    public BaseResult authenticationException(AuthenticationException e) {

        return new BaseResult(SystemParameters.FAIL.getIndex(), e.getMessage(), e.getCode());
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    BaseResult exception(Exception e) {

        return new BaseResult(SystemParameters.FAIL.getIndex(), e.getMessage(), SystemParameters.ERROR.getIndex());
    }

}

注意:

     由於使用filter過濾器,springmvc 不能進行filter攔截異常,故通過請求轉發來實現統一異常攔截

八、錯誤異常處理,用來處理filter轉發出來的請求攔截異常,進而讓springmvc統一處理異常

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * 錯誤控制器
 *
 * @author kou
 */
@RestController
public class ErrorController {

    @RequestMapping("/exception/authentication")
    public BaseResult authenticationException(HttpServletRequest request) {

        throw new AuthenticationException(Integer.valueOf(request.getAttribute("exceptionCode").toString()), request.getAttribute("exceptionMessage").toString());
    }
}

九、返回統一格式


public class BaseResult {

    // 結果狀態碼
    private int ret;
    // 結果說明
    private String msg;

    private int err;

    public BaseResult() {
    }

    public BaseResult(int ret, String msg, int err) {
        this.ret = ret;
        this.msg = msg;
        this.err = err;
    }

    public int getRet() {
        return ret;
    }

    public void setRet(int ret) {
        this.ret = ret;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getErr() {
        return err;
    }

    public void setErr(int err) {
        this.err = err;
    }

}

 

 

 

 

 

 

 

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