深刻總結SpringBoot整合JWT,這應該是全網講的最通俗易懂的了

JWT

JWT(JSON Web Token)是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準。java

舉例登陸過程



在這裏我的整理了一些資料,有須要的朋友能夠直接點擊領取。面試

Java基礎知識大全算法

22本Java架構師核心書籍spring

從0到1Java學習路線和資料緩存

1000+道2021年最新面試題服務器

組成

JWT具體長什麼樣呢?
JWT是由三段信息構成的,將這三段信息文本用.連接一塊兒就構成了JWT字符串。就像這樣:網絡

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

元素

header

JWT的頭部承載兩部分信息:架構

  • 聲明類型,這裏是JWT;
  • 聲明加密的算法,一般直接使用 HMAC SHA256;
  • 完整的頭部就像下面這樣的JSON:app

    {
      'typ': 'JWT',
      'alg': 'HS256'
    }

    使用base64加密,構成了第一部分。dom

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

    playload(重點)

    載荷就是存放有效信息的地方,這些有效信息包含三個部分:

  • 標準中註冊的聲明;
  • 公共的聲明;
  • 私有的聲明;
    其中,標準中註冊的聲明 (建議但不強制使用)包括以下幾個部分 :
  • iss: jwt簽發者;
  • sub: jwt所面向的用戶;
  • aud: 接收jwt的一方;
  • exp: jwt的過時時間,這個過時時間必需要大於簽發時間;
  • nbf: 定義在什麼時間以前,該jwt都是不可用的;
  • iat: jwt的簽發時間;
  • jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊;
    公共的聲明部分:公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息,但不建議添加敏感信息,由於該部分在客戶端可解密。

私有的聲明部分:私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。

定義一個payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

而後將其進行base64加密,獲得Jwt的第二部分:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
 
 
var signature = HMACSHA256(encodedString, '密鑰');
加密以後,獲得signature簽名信息。

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

jwt最終格式

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

secret用來進行jwt的簽發和jwt的驗證,因此,在任何場景都不該該流露出去。

元素

SpringBoot整合JWT【正片】

引入依賴

<!--token-->
    <!-- jwt支持 -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
    </dependency>

建立JWT工具類

注意靜態屬性的配置文件注入方式:

package com.neuq.common.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.neuq.common.exception.ApiException;
import com.neuq.common.response.ResultInfo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author: xiang
 * @Date: 2021/5/11 21:11
 * <p>
 * JwtToken生成的工具類
 * JWT token的格式:header.payload.signature
 * header的格式(算法、token的類型),默認:{"alg": "HS512","typ": "JWT"}
 * payload的格式 設置:(用戶信息、建立時間、生成時間)
 * signature的生成算法:
 * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
 */

@Component
@ConfigurationProperties(prefix = "jwt")
public class JWTUtils {

    //定義token返回頭部
    public static String header;

    //token前綴
    public static String tokenPrefix;

    //簽名密鑰
    public static String secret;

    //有效期
    public static long expireTime;

    //存進客戶端的token的key名
    public static final String USER_LOGIN_TOKEN = "USER_LOGIN_TOKEN";

    public void setHeader(String header) {
        JWTUtils.header = header;
    }

    public void setTokenPrefix(String tokenPrefix) {
        JWTUtils.tokenPrefix = tokenPrefix;
    }

    public void setSecret(String secret) {
        JWTUtils.secret = secret;
    }

    public void setExpireTime(int expireTimeInt) {
        JWTUtils.expireTime = expireTimeInt*1000L*60;
    }

    /**
     * 建立TOKEN
     * @param sub
     * @return
     */
    public static String createToken(String sub){
        return tokenPrefix + JWT.create()
                .withSubject(sub)
                .withExpiresAt(new Date(System.currentTimeMillis() + expireTime))
                .sign(Algorithm.HMAC512(secret));
    }


    /**
     * 驗證token
     * @param token
     */
    public static String validateToken(String token){
        try {
            return JWT.require(Algorithm.HMAC512(secret))
                    .build()
                    .verify(token.replace(tokenPrefix, ""))
                    .getSubject();
        } catch (TokenExpiredException e){
            throw new ApiException(ResultInfo.unauthorized("token已通過期"));
        } catch (Exception e){
            throw new ApiException(ResultInfo.unauthorized("token驗證失敗"));
        }
    }

    /**
     * 檢查token是否須要更新
     * @param token
     * @return
     */
    public static boolean isNeedUpdate(String token){
        //獲取token過時時間
        Date expiresAt = null;
        try {
            expiresAt = JWT.require(Algorithm.HMAC512(secret))
                    .build()
                    .verify(token.replace(tokenPrefix, ""))
                    .getExpiresAt();
        } catch (TokenExpiredException e){
            return true;
        } catch (Exception e){
            throw new ApiException(ResultInfo.unauthorized("token驗證失敗"));
        }
        //若是剩餘過時時間少於過時時常的通常時 須要更新
        return (expiresAt.getTime()-System.currentTimeMillis()) < (expireTime>>1);
    }
}

yaml屬性配置

jwt:
  header: "Authorization" #token返回頭部
  tokenPrefix: "Bearer " #token前綴
  secret: "qwertyuiop7418520" #密鑰
  expireTime: 1 #token有效時間 (分鐘) 建議一小時以上

登陸方法將用戶信息存入token中返回

@Override
  public Map<String,Object> login(User user) {
      //phone是除id外的惟一標誌 須要進行檢查
      if (user.getPhone() == null || user.getPhone().equals(""))
          throw new ApiException("手機號不合法");
      User selectUser = userDao.selectUserByPhone(user.getPhone());
      if (selectUser == null) {
          //註冊用戶
          int count = userDao.insertUser(user);
          if (count < 1) throw new ApiException(ResultInfo.serviceUnavailable("註冊異常"));
      }
      //將userId存入token中
      String token = JWTUtils.createToken(selectUser.getUserId().toString());
      Map<String,Object> map = new HashMap<>();
      map.put("user",selectUser);
      map.put("token",token);
      return map;
  }

注意將token保存到Http 的 header

@GetMapping("/login")
    public ResultInfo login(User user, HttpServletResponse response) {
        Map<String, Object> map = userService.login(user);
        //將token存入Http的header中
        response.setHeader(JWTUtils.USER_LOGIN_TOKEN, (String) map.get("token"));
        return ResultInfo.success((User)map.get("user"));
    }

攔截器驗證每次請求的token

/**
 * @Author: xiang
 * @Date: 2021/5/7 20:56
 * <p>
 * 攔截器:驗證用戶是否登陸
 */
public class UserLoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //http的header中得到token
        String token = request.getHeader(JWTUtils.USER_LOGIN_TOKEN);
        //token不存在
        if (token == null || token.equals("")) throw new ApiException("請先登陸");
        //驗證token
        String sub = JWTUtils.validateToken(token);
        if (sub == null || sub.equals(""))
            throw new ApiException(ResultInfo.unauthorized("token驗證失敗"));
        //更新token有效時間 (若是須要更新其實就是產生一個新的token)
        if (JWTUtils.isNeedUpdate(token)){
            String newToken = JWTUtils.createToken(sub);
            response.setHeader(JWTUtils.USER_LOGIN_TOKEN,newToken);
        }
        return true;
    }
}
@Configuration
@ComponentScan(basePackages = "com.neuq.common") //全局異常處理類須要被掃描才能
public class WebMvcConfig implements WebMvcConfigurer {
    /**
     * 註冊自定義攔截器
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserLoginInterceptor())
                .addPathPatterns("/user/**")
                .addPathPatterns("/userInfo/**")
                .excludePathPatterns("/user/login");//開放登陸路徑
    }

}

單點登陸

將token或者一個惟一標識UUID=UUID.randomUUID().toString()存進Cookie中(別存在Http的header中了),設置路徑爲整個項目根路徑/*;
每每以這個惟一標識爲key,用戶信息爲value緩存在服務器中!!!

最後

感謝大佬能看到最後,以爲文章對你有幫助記得點個贊!

相關文章
相關標籤/搜索