JWT詳解
1. 介紹
簡稱
JWT
,也就是通過**
JSON Web Token
**,用于各方之間安全地将資訊作為JSON對象傳輸,在資料傳輸的過程中還可以完成資料加密、簽名等相關處理。
JSON形式作為Web應用中的令牌
2. 流程圖
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主: 程式設計不良人