天天看點

JWT詳解JWT詳解

JWT詳解

1. 介紹

  • JWT

    簡稱

    JSON Web Token

    ,也就是通過**

    JSON形式作為Web應用中的令牌

    **,用于各方之間安全地将資訊作為JSON對象傳輸,在資料傳輸的過程中還可以完成資料加密、簽名等相關處理。

2. 流程圖

JWT詳解JWT詳解

2.1 認證流程

  • 首先,前端通過Web表單将自己的使用者名和密碼發送到後端的接口。這一過程一般是一個HTTP POST請求。建議的方式是通過SSL加密的傳輸(https協定),進而避免敏感資訊被嗅探。
  • 後端核對使用者名和密碼成功後,将使用者的id等其他資訊作為 JWT Payload(負載),将其與頭部分别進行Base64編碼拼接後簽名,形成一個JWT(Token)。形成的JWT就是一個形同lll.mmm.sss的字元串。

    token = head.payload.singurater

  • 後端将JWT字元串作為登入成功的傳回結果傳回給前端。前端可以将傳回的結果儲存在localStorage或sessionStorage上,登出時前端删除儲存的JWT即可。
  • 前端在每次請求時将JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題)
  • 後端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正确;檢查Token是否過期;檢查Token的接收方是否是自己(可選)。
  • 驗證通過後後端使用JWT中包含的使用者資訊進行其他邏輯操作,傳回相應結果。

2.2 jwt優勢

  • 簡潔(Compact): 可以通過URL,POST參數或者在HTTP header發送,因為資料量小,傳輸速度也很快
  • 自包含(Self-contained):負載中包含了所有使用者所需要的資訊,避免了多次查詢資料庫
  • 因為Token是以JSON加密的形式儲存在用戶端的,是以JWT是跨語言的,原則上任何web形式都支援。
  • 不需要在服務端儲存會話資訊,特别适用于分布式微服務。

3. JWT

3.1 header(表頭)

  • 标頭通常由兩部分組成:令牌的類型(即JWT)和所使用的簽名算法,例如HMAC SHA256(預設)或RSA。它會使用Base64編碼組成JWT結構的第一部分。
  • 注意:Base64是一種編碼,也就是說,它是可以被翻譯回原來的樣子的,它并不是一種加密過程。

3.2 Payload(載荷)

  • 将能用到的使用者資訊放在 Payload中。官方建議不要放特别敏感的資訊,例如密碼。

3.3 Signature(簽名資訊)

  • 簽證由三部分組成,header和payload分别經base64Url(一種在base64上做了一點改變的編碼)編碼後由’.’連接配接,伺服器生成秘鑰(secret),連接配接之後的字元串在經header中聲明的加密方式和秘鑰加密,再用’.'和加密前的字元串連接配接。伺服器驗證token時隻會驗證第三部分。
  • header (base64Url) . payload (base64Url) . secret(header (base64Url)+payload (base64Url))

4. Session方式

  • session方法存儲會有很大的缺點。略
@RestController
@RequestMapping("test")
public class SessionController {
    @PostMapping("login")
    public String login(String userName, String password, HttpServletRequest request){
        // 将使用者名和密碼拼接後設定在session作用域中
        request.getSession().setAttribute("user", userName + password);
        return "Login Success!";
    }
}
           

5. Jwt實作

5.1 依賴

<!--	jwt的依賴包	-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
           

5.2 工具類

package com.liu.jwt.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

/**
 * @author lms
 * @date 2021-10-02 - 8:46
 *
 * jwt工具類
 */
public class JwtUtils {

    // sign
    private static final String SIGN = "[email protected]";


    // 生成token,header.payload.sign
    public static String getToken(Map<String, String> map){

        // 建立jwt builder對象
        JWTCreator.Builder builder = JWT.create();

        // 添加payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });

        // 設定過期時間
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7);
        // 設定過期時間和添加簽名資訊
        String token = builder.withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256(SIGN));
        return token;
    }

    // 驗證token的合法性
    public static void verify(String token){
        // 如果驗證失敗會直接抛出異常資訊
        JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
    }

    // 擷取token中的資訊(也可以和上面部分的代碼直接合并)
    public static DecodedJWT getTokenInfo(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
        return verify;
    }
}
           

5.3 攔截器

package com.liu.jwt.interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liu.jwt.util.JwtUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

/**
 * @author lms
 * @date 2021-10-02 - 10:21
 * jwt攔截器,執行token的驗證工作(集的配置攔截器)
 */
public class JwtInterceptor implements HandlerInterceptor {

    /**
     * 在目标方法執行之前執行
     * @param request
     * @param response
     * @param handler 目标方法
     * @return true,代表放行, false,表示禁止放行
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HashMap<String, Object> map = new HashMap<>();
        // 因為token存放在請求頭中,
        String token = request.getHeader("token");

        // 驗證token
        try {
            // 直接驗證token是否正确,如果token不正确,會直接抛出異常資訊被全局異常捕獲
            JwtUtils.verify(token);
            // 驗證通過,直接放行請求
            return true;

        } catch (SignatureVerificationException e){
            map.put("msg", "無效簽名");
        } catch (TokenExpiredException e){
            map.put("msg", "token已過期");
        } catch (AlgorithmMismatchException e){
            map.put("msg", "token算法不一緻");
        } catch (Exception e) {
            map.put("msg", "token無效");
        }
        map.put("code", "500");
        // 将map轉為json資料資訊
        // 因為response底層也封裝了json,也可以使用fastJson
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().println(json);
        // 禁止放行請求
        return false;
    }
}
           
  • 配置攔截器
package com.liu.jwt.config;

import com.liu.jwt.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author lms
 * @date 2021-10-02 - 10:29
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    // 配置攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JwtInterceptor())
                // 攔截所有的請求資訊
                .addPathPatterns("/**")
                // 隻放行/login登入請求,
                .excludePathPatterns("/login");
    }
}
           

5.4 Controller

package com.liu.jwt.controller;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.liu.jwt.entity.User;
import com.liu.jwt.service.UserService;
import com.liu.jwt.util.JwtUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lms
 * @date 2021-10-02 - 9:24
 */
@RestController
public class UserController {

    @Resource
    private UserService userService;

    /**
     * 使用者登入
     * @param user
     * @return
     */
    @PostMapping("login")
    public Map<String, Object> login(User user){
        HashMap<String, Object> map = new HashMap<>();
        System.out.println("user.getUsername() = " + user.getUsername());
        System.out.println("user.getPassword() = " + user.getPassword());

        try {
            User userLogin = userService.login(user);
            // 使用者存放負載資訊,進而調用jwt生成相應的token資訊
            HashMap<String, String> payload = new HashMap<>();
            payload.put("id", userLogin.getId());
            payload.put("username", userLogin.getUsername());

            // 生成token資訊
            String token = JwtUtils.getToken(payload);

            map.put("code", "200");
            map.put("msg","登入成功");
            map.put("token", token);
        } catch (Exception e) {
            map.put("code", "500");
            map.put("msg", e.getMessage());
        }
        return map;
    }


    // 方式1:未使用攔截器,代碼備援
    // 登入之後執行相應的業務邏輯
	//    @PostMapping("test")
    public Map<String, Object> verify(String token){
        HashMap<String, Object> map = new HashMap<>();
        System.out.println("token = " + token);

        // 驗證token
        try {
            JwtUtils.verify(token);
            // 擷取token的傳回值資訊,類型為DecodedJWT
            DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);
            map.put("code", "200");
            map.put("msg","請求成功");
            return map;
        } catch (SignatureVerificationException e){
            map.put("msg", "無效簽名");
        } catch (TokenExpiredException e){
            map.put("msg", "token已過期");
        } catch (AlgorithmMismatchException e){
            map.put("msg", "token算法不一緻");
        } catch (Exception e) {
            map.put("msg", "token無效");
        }
        map.put("code", "500");
        return map;
    }


    // 方式2:配置了攔截器(隻有登入之後才能進行使用該方法)
    @PostMapping("test")
    public Map<String, Object> test(HttpServletRequest request){
        HashMap<String, Object> map = new HashMap<>();
        // 從請求頭中擷取token資訊
        String token = request.getHeader("token");
        DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);

        // 處理自己的業務邏輯
        map.put("code", "200");
        map.put("msg","請求成功");

        String username = tokenInfo.getClaim("username").asString();
        String id = tokenInfo.getClaim("id").asString();
        map.put("username", username);
        map.put("id", id);

        return map;
    }
}
           
  • 筆記整理來源于B站Up主: 程式設計不良人