SpringBoot 整合 JWT Token


   之前寫了一篇JWT的隨筆, 還沒有寫springboot 整合 token,  前幾天晚上忙着抽空重構代碼, 就簡單的重構了下方法, 類. 還有if else,  + 設計模式, +泛型沒整. 等周末有時間再整整.   其實理解了jwt流程, 整合進springboot就相對於來說較簡單了.  依賴加下, 攔截器加下, 代碼加下. 話不多說, 上代碼

   pom.xml 依賴

		<!-- jwt-->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.0</version>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

  application.properties 配置

##jwt配置
# 代表這個JWT的接收對象,存入audience
audience.clientId=
# 密鑰, 經過Base64加密, 可自行替換
audience.base64Secret==
# JWT的簽發主體,存入issuer
audience.name=
# 過期時間,時間戳
audience.expiresSecond=

    攔截器  

import com.spSystem.annotation.JwtIgnore;
import com.spSystem.common.exception.CustomException;
import com.spSystem.common.exception.response.ResultCode;
import com.spSystem.entity.Audience;
import com.spSystem.utils.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * token驗證攔截器
 */
@Slf4j
public class JwtInterceptor extends HandlerInterceptorAdapter {

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

    @Autowired
    private Audience audience;

    @Autowired
    private RedisTemplate redisTemplate;

    public static String lastToken = "";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 忽略帶JwtIgnore注解的請求, 不做后續token認證校驗
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            JwtIgnore jwtIgnore = handlerMethod.getMethodAnnotation(JwtIgnore.class);
            if (jwtIgnore != null) {
                return true;
            }
        }

        if (HttpMethod.OPTIONS.equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }

        // 獲取請求頭信息authorization信息
        final String authHeader = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
        log.info("## authHeader= {}", authHeader);

        if (StringUtils.isBlank(authHeader) || !authHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
            log.info("### 用戶未登錄,請先登錄 ###");
            throw new CustomException(ResultCode.USER_NOT_LOGGED_IN);
        }

        // 獲取token
        final String token = authHeader.substring(7);

        if (audience == null) {
            BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
            audience = (Audience) factory.getBean("audience");
        }

        // 驗證token是否有效--無效已做異常拋出,由全局異常處理后返回對應信息
//        JwtTokenUtil.parseJWT(token, audience.getBase64Secret());

   }

}

   WebConfig  

import com.spSystem.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer  {


    @Bean
    public JwtInterceptor getInterceptor() {
        return new JwtInterceptor();

    }

    /**
     * 添加攔截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //攔截路徑可自行配置多個 可用 ,分隔開
        registry.addInterceptor(getInterceptor()).addPathPatterns("/**");
    }

    /**
     * 跨域支持
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD")
                .maxAge(3600 * 24);
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {

    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {

    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {

    }

    @Override
    public void addFormatters(FormatterRegistry formatterRegistry) {

    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {

    }

    @Override
    public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {

    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {

    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {

    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public Validator getValidator() {
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }

}

  JWT驗證忽略注解

import java.lang.annotation.*;

/**
 * JWT驗證忽略注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JwtIgnore {
}

  全局異常處理器

import com.spSystem.common.exception.CustomException;
import com.spSystem.common.exception.response.Result;
import com.spSystem.common.exception.response.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;

/**
 * 全局異常處理器
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

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

    /**
     * 處理自定義異常
     */
    @ExceptionHandler(CustomException.class)
    public Result handleException(CustomException e) {
        // 打印異常信息
        log.error("### 異常信息:{} ###", e.getMessage());
        return new Result(e.getResultCode());
    }

    /**
     * 參數錯誤異常
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public Result handleException(Exception e) {

        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
            BindingResult result = validException.getBindingResult();
            StringBuffer errorMsg = new StringBuffer();
            if (result.hasErrors()) {
                List<ObjectError> errors = result.getAllErrors();
//                errors.forEach(p ->{
//                    FieldError fieldError = (FieldError) p;
//                    errorMsg.append(fieldError.getDefaultMessage()).append(",");
//                    log.error("### 請求參數錯誤:{"+fieldError.getObjectName()+"},field{"+fieldError.getField()+ "},errorMessage{"+fieldError.getDefaultMessage()+"}"); });
            }
        } else if (e instanceof BindException) {
            BindException bindException = (BindException)e;
            if (bindException.hasErrors()) {
                log.error("### 請求參數錯誤: {}", bindException.getAllErrors());
            }
        }

        return new Result(ResultCode.PARAM_IS_INVALID);
    }

    /**
     * 處理所有不可知的異常
     */
    @ExceptionHandler(Exception.class)
    public Result handleOtherException(Exception e){
        //打印異常堆棧信息
        e.printStackTrace();
        // 打印異常信息
        log.error("### 不可知的異常:{} ###", e.getMessage());
        return new Result(ResultCode.SYSTEM_INNER_ERROR);
    }

}

    自定義異常類型

import com.spSystem.common.exception.response.ResultCode;

import java.text.MessageFormat;

/**
 * 自定義異常類型
 **/
public class CustomException extends RuntimeException {

    //錯誤代碼
    ResultCode resultCode;

    public CustomException(ResultCode resultCode){
        super(resultCode.message());
        this.resultCode = resultCode;
    }

    public CustomException(ResultCode resultCode, Object... args){
        super(resultCode.message());
        String message = MessageFormat.format(resultCode.message(), args);
        resultCode.setMessage(message);
        this.resultCode = resultCode;
    }

    public ResultCode getResultCode(){
        return resultCode;
    }

}

    統一響應結果集

import com.fasterxml.jackson.databind.annotation.JsonSerialize;

/**
 * 統一響應結果集
 */
@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class Result<T> {

    //操作代碼
    int code;

    //提示信息
    String message;

    //結果數據
    T data;

    public Result(ResultCode resultCode){
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public Result(ResultCode resultCode, T data){
        this.code = resultCode.code();
        this.message = resultCode.message();
        this.data = data;
    }

    public Result(String message){
        this.message = message;
    }

    public static Result SUCCESS(){
        return new Result(ResultCode.SUCCESS);
    }

    public static <T> Result SUCCESS(T data){
        return new Result(ResultCode.SUCCESS, data);
    }

    public static Result FAIL(){
        return new Result(ResultCode.FAIL);
    }

    public static Result FAIL(String message){
        return new Result(message);
    }

    public int getCode() {
        return code;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

  通用響應狀態

/**
 * 通用響應狀態
 */
public enum ResultCode {

    /* 成功狀態碼 */
    SUCCESS(200,"操作成功"),

    /* 錯誤狀態碼 */
    FAIL(500,"操作失敗"),

    /* 參數錯誤:10001-19999 */
    PARAM_IS_INVALID(1001, "參數無效"),
    PARAM_IS_BLANK(1002, "參數為空"),
    PARAM_TYPE_BIND_ERROR(1003, "參數格式錯誤"),
    PARAM_NOT_COMPLETE(1004, "參數缺失"),

    /* 用戶錯誤:20001-29999*/
    USER_NOT_LOGGED_IN(2001, "用戶未登錄,請先登錄"),
    USER_LOGIN_ERROR(2002, "賬號不存在或密碼錯誤"),
    USER_ACCOUNT_FORBIDDEN(2003, "賬號已被禁用"),
    USER_NOT_EXIST(2004, "用戶不存在"),
    USER_HAS_EXISTED(2005, "用戶已存在"),

    /* 業務錯誤:30001-39999 */
    BUSINESS_GROUP_NO_ALLOWED_DEL(30001, "應用分組已經被應用使用,不能刪除"),
    BUSINESS_THEME_NO_ALLOWED_DEL(30002, "主題已經被用戶使用,不能刪除"),
    BUSINESS_THEME_NO_ALLOWED_DISABLE(30003, "主題已經被用戶使用,不能停用"),
    BUSINESS_THEME_DEFAULT_NO_ALLOWED_DEL(30004, "默認主題,不能刪除"),
    BUSINESS_THEME_NO_ALLOWED_UPDATE(30005, "主題已經被用戶使用,不能修改圖片信息"),
    BUSINESS_IS_TOP(30040, "已經到最頂部"),
    BUSINESS_IS_BOTTOM(30041, "已經到最底部"),
    BUSINESS_NAME_EXISTED(30051, "名稱已存在"),

    /* 系統錯誤:40001-49999 */
    SYSTEM_INNER_ERROR(40001, "系統繁忙,請稍后重試"),
    UPLOAD_ERROR(40002, "系統異常,上傳文件失敗"),
    FILE_MAX_SIZE_OVERFLOW(40003, "上傳尺寸過大"),
    FILE_ACCEPT_NOT_SUPPORT(40004, "上傳文件格式不支持"),
    SET_UP_AT_LEAST_ONE_ADMIN(40005, "至少指定一個管理員"),
    URL_INVALID(40006, "地址不合法"),
    LINK_AND_LOGOUT_NO_MATCH(40006, "主頁地址和注銷地址IP不一致"),
    IP_AND_PORT_EXISTED(40007, "當前IP和端口已經被占中"),
    LINK_IS_REQUIRED(40008, "生成第三方token認證信息:主頁地址不能為空,請完善信息"),
    ONLY_ROOT_DEPARTMENT(40009, "組織機構只能存在一個根機構"),
    DEPART_CODE_EXISTED(40010, "組織機構編碼已存在"),
    DEPART_CONTAINS_USERS(40011, "該機構下是存在用戶,不允許刪除"),
    DEPART_CONTAINS_SON(40012, "該機構下是存在子級機構,不允許刪除"),
    DEPART_PARENT_IS_SELF(40013, "選擇的父機構不能為本身"),
    DICT_EXIST_DEPEND(40014, "該字典數據存在詳情依賴,不允許刪除"),
    DICT_DETAIL_LOCK(40015, "該字典數據被鎖定,不允許修改或刪除"),
    DEPART_CODE_EXISTED_WITH_ARGS(40016, "組織機構編碼【{0}】系統已存在"),

    /* 數據錯誤:50001-599999 */
    RESULT_DATA_NONE(50001, "數據未找到"),
    DATA_IS_WRONG(50002, "數據有誤"),
    DATA_ALREADY_EXISTED(50003, "數據已存在"),

    /* 接口錯誤:60001-69999 */
    INTERFACE_INNER_INVOKE_ERROR(60001, "內部系統接口調用異常"),
    INTERFACE_OUTTER_INVOKE_ERROR(60002, "外部系統接口調用異常"),
    INTERFACE_FORBID_VISIT(60003, "該接口禁止訪問"),
    INTERFACE_ADDRESS_INVALID(60004, "接口地址無效"),
    INTERFACE_REQUEST_TIMEOUT(60005, "接口請求超時"),
    INTERFACE_EXCEED_LOAD(60006, "接口負載過高"),


    /* 權限錯誤:70001-79999 */
    PERMISSION_UNAUTHENTICATED(70001,"此操作需要登陸系統"),
    PERMISSION_UNAUTHORISE(70002,"權限不足,無權操作"),
    PERMISSION_EXPIRE(70003,"登錄狀態過期"),
    PERMISSION_TOKEN_EXPIRED(70004, "token已過期"),
    PERMISSION_LIMIT(70005, "訪問次數受限制"),
    PERMISSION_TOKEN_INVALID(70006, "無效token"),
    PERMISSION_SIGNATURE_ERROR(70007, "簽名失敗");

    //操作代碼
    int code;
    //提示信息
    String message;
    ResultCode(int code, String message){
        this.code = code;
        this.message = message;
    }

    public int code() {
        return code;
    }

    public String message() {
        return message;
    }

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

    public void setMessage(String message) {
        this.message = message;
    }
}

  JwtTokenUtil

import com.spSystem.common.exception.CustomException;
import com.spSystem.common.exception.response.ResultCode;
import com.spSystem.entity.Audience;
import com.spSystem.model.CdAppUsers;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class JwtTokenUtil {

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

    public static final String AUTH_HEADER_KEY = "Authorization";

    public static final String TOKEN_PREFIX = "Bearer ";

    /**
     * 解析jwt
     */
    public static Claims parseJWT(String jsonWebToken, String base64Security) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
                    .parseClaimsJws(jsonWebToken).getBody();
            return claims;
        } catch (ExpiredJwtException eje) {
            log.error("===== Token過期 =====", eje);
            throw new CustomException(ResultCode.PERMISSION_TOKEN_EXPIRED);
        } catch (Exception e){
            log.error("===== token解析異常 =====", e);
            throw new CustomException(ResultCode.PERMISSION_TOKEN_INVALID);
        }
    }

    /**
     * 構建jwt
     */
    public static String createJWT(String userId, String phone, String role,Audience audience) {
        try {
            // 使用HS256加密算法
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);

            //生成簽名密鑰
            byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(audience.getBase64Secret());
            Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

            //userId是重要信息,進行加密下
            String encryId = Base64Util.encode(userId);

            //添加構成JWT的參數
            JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
                    // 可以將基本不重要的對象信息放到claims
                    .claim("role", role)
                    .claim("userId", userId)
                    .setSubject(phone)           // 代表這個JWT的主體,即它的所有人
                    .setIssuer(audience.getClientId())              // 代表這個JWT的簽發主體;
                    .setIssuedAt(new Date())        // 是一個時間戳,代表這個JWT的簽發時間;
                    .setAudience(audience.getName())          // 代表這個JWT的接收對象;
                    .signWith(signatureAlgorithm, signingKey);
            //添加Token過期時間
            int TTLMillis = audience.getExpiresSecond();
            if (TTLMillis >= 0) {
                long expMillis = nowMillis + TTLMillis;
                Date exp = new Date(expMillis);
//                builder.setExpiration(exp)  // 是一個時間戳,代表這個JWT的過期時間;
                      builder.setNotBefore(now); // 是一個時間戳,代表這個JWT生效的開始時間,意味着在這個時間之前驗證JWT是會失敗的
            }

            //生成JWT
            return builder.compact();
        } catch (Exception e) {
            log.error("簽名失敗", e);
            throw new CustomException(ResultCode.PERMISSION_SIGNATURE_ERROR);
        }
    }

    /**
     * 從token中獲取用戶名
     */
    public static String getPhone(String token, String base64Security){
        return parseJWT(token, base64Security).getSubject();
    }

    /**
     * 從token中獲取用戶ID
     */
    public static String getUserId(String token, String base64Security){
        return  parseJWT(token, base64Security).get("userId", String.class);
    }

    /**
     * 是否已過期
     */
    public static boolean isExpiration(String token, String base64Security) {
        return parseJWT(token, base64Security).getExpiration().before(new Date());
    }

}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM