JWT簡介
背景
在傳統的有狀態服務應用中,服務端需要記錄每次會話的客戶端資訊,從而識別客戶端身份,根據用戶身份進行請求的處理,典型的設計如Tomcat中的Session,例如登錄:用戶登錄后,我們把用戶的資訊保存在服務端session中,并且給用戶一個cookie值,記錄對應的session,然后下次請求,用戶攜帶cookie值來(這一步有瀏覽器自動完成),我們就能識別到對應session,從而找到用戶的資訊,這種方式目前來看最方便,但在分布式應用中,由服務端保存用戶狀態不是一種很好的選擇,因此JWT誕生
JWT概述
JWT(JSON WEB Token)是一個標準,借助JSON格式資料作為WEB應用請求中的令牌,進行資料的自包含設計,實作各方安全的資訊傳輸,在資料傳輸程序中還可以對資料進行加密,簽名等相關處理,同時JWT也是目前最流行的跨域身份驗證解決方案(其官方網址為:https://jwt.io/),可以非常方便的在分布式系統中實作用戶身份認證,
JWT資料結構
JWT通常由三部分構成,分別為Header(頭部),Payload(負載),Signature(簽名),其格式如下:
xxxxx.yyyyy.zzzzz
例如:
eyJhbGciOiJIUzI1NiJ9.eyJwZXJtaXNzaW9ucyI6InN5czpyZXM6Y3JlYXRlLHN5czpyZXM6cmV0cmlldmUiLCJleHAiOjE2MjY5MzIyNTksImlhdCI6MTYyNjkzMDQ1OSwidXNlcm5hbWUiOiJqYWNrIn0.SQrRS5nuID1Xv5GMvUgnr7xrVzB7GcRFrkNak-x16Mw
Header部分
Header 部分是一個 JSON 物件,描述 JWT 的元資料,通常是下面的樣子,
{
"alg": "HS256",
"typ": "JWT"
}
上面代碼中,alg屬性表示簽名的演算法(algorithm),默認是 HMAC SHA256(簡寫HS256);typ屬性表示這個令牌(token)的型別(type),JWT 令牌統一寫為JWT,最后,將這個 JSON 物件使用 Base64URL 演算法(詳見后文)轉成字串,
Payload部分
Payload 部分也是一個 JSON 物件,用來存放實際需要傳遞的資料,JWT規范中規定了7個官方欄位,供選用,
? iss (issuer):簽發人
? exp (expiration time):過期時間
? sub (subject):主題
? aud (audience):受眾
? nbf (Not Before):生效時間
? iat (Issued At):簽發時間
? jti (JWT ID):編號
除了官方欄位,你還可以在這個部分定義私有欄位,下面就是一個例子,
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意,JWT 默認是不加密的,任何人都可以讀到,所以不要把秘密資訊放在這個部分,
這個 JSON 物件也要使用 Base64URL 演算法轉成字串,
Signature部分
Signature 部分是對前兩部分的簽名,其目的是防止資料被篡改,
首先,需要指定一個密鑰(secret),這個密鑰只有服務器才知道,不能泄露給用戶,然后,使用 Header 里面指定的簽名演算法(默認是 HMAC SHA256),按照下面的公式產生簽名,
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出簽名以后,把 Header、Payload、Signature 三個部分拼成一個字串,每個部分之間用"點"(.)分隔,就可以回傳給用戶,
JWT快速入門
環境準備
第一步:創建專案,例如:

第二步,添加依賴,其pom.xml檔案內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.2.RELEASE</version>
</parent>
<groupId>com.cy</groupId>
<artifactId>03-jt-security-jwt</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--添加jwt依賴-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
</project>
第三步:創建組態檔application.yml (暫時不寫任何內容)
第四步:定義啟動類,代碼如下
package com.cy.jt;
@SpringBootApplication
public class SecurityJwtApplication {
public static void main(String[] args) {
SpringApplication.run(
SecurityJwtApplication.class,
args);
}
}
第四步:運行啟動類,檢測是否可成功啟動
創建和決議token
撰寫單元測驗,實踐Token物件的創建與決議,例如:
@Test
void testCreateAndParseToken(){
//1.創建令牌
//1.1定義負載資訊
Map<String,Object> map=new HashMap<>();
map.put("username", "jack");
map.put("permissions", "sys:res:create,sys:res:retrieve");
//1.2定義過期實踐
Calendar calendar=Calendar.getInstance();
calendar.add(Calendar.MINUTE, 30);
Date expirationTime=calendar.getTime();
//1.3定義密鑰
String secret="AAABBBCCCDDD";
//1.4生成令牌
String token= Jwts.builder()
.setClaims(map)
.setIssuedAt(new Date())
.setExpiration(calendar.getTime())
.signWith(SignatureAlgorithm.HS256,secret)
.compact();
System.out.println(token);
//2.決議令牌
Claims claims = Jwts.parser().setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
System.out.println("claims="+claims);
}
創建JWT工具類
為了簡化JWT在專案中的應用,我們通常會構建一個工具類,對token的創建和決議進行封裝,例如:
package com.cy.jt.security.util;
public class JwtUtils {
/**
* 秘鑰
*/
private static String secret="AAABBBCCCDDDEEE";
/**
* 有效期,單位秒
* 默認30分鐘
*/
private static Long expirationTimeInSecond=1800L;
/**
* 從token中獲取claim
*
* @param token token
* @return claim
*/
public static Claims getClaimsFromToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secret.getBytes())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
throw new IllegalArgumentException("Token invalided.");
}
}
/**
* 判斷token是否過期
* @param token token
* @return 已過期回傳true,未過期回傳false
*/
private static Boolean isTokenExpired(String token) {
Date expiration = getClaimsFromToken(token).getExpiration();
return expiration.before(new Date());
}
/**
* 為指定用戶生成token
* @param claims 用戶資訊
* @return token
*/
public static String generateToken(Map<String, Object> claims) {
Date createdTime = new Date();
Date expirationTime = new Date(System.currentTimeMillis() + expirationTimeInSecond * 1000);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(createdTime)
.setExpiration(expirationTime)
.signWith(SignatureAlgorithm.HS256,secret)
.compact();
}
}
JWT在專案中的應用
AuthController 認證服務
定義AuthController用于處理登錄認證業務,代碼如下:
package com.cy.jt.security.controller;
@RestController
public class AuthController {
@RequestMapping("/login")
public Map<String,Object> doLogin(String username,
String password){
Map<String,Object> map=new HashMap<>();
if("jack".equals(username)&&"123456".equals(password)){
map.put("state","200");
map.put("message","login ok");
Map<String,Object> claims=new HashMap<>();//負載資訊
claims.put("username",username);
map.put("Authentication", JwtUtils.generatorToken(claims));
return map;
}else{
map.put("state","500");
map.put("message","login failure");
return map;
}
}
}
ResourceController 資源服務
定義一個資源服務物件,登錄成功以后可以訪問此物件中的方法,例如:
package com.cy.jt.security.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ResourceController {
@RequestMapping("/retrieve")
public String doRetrieve(){
//檢查用戶有沒有登錄
//執行業務查詢操作
return "do retrieve resource success";
}
@RequestMapping("/update")
public String doUpdate(){
//檢查用戶有沒有登錄
//執行業務查詢操作
return "do update resource success";
}
}
TokenInterceptor 攔截器及配置
假如在每個方法中都去校驗用戶身份的合法性,代碼冗余會比較大,我們可以寫一個Spring MVC 攔截器,
在攔截器中進行用戶身份檢測,例如:
package com.cy.jt.security.interceptor;
import com.cy.jt.security.util.JwtUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 令牌(token:ticker-通票)攔截器
* 其中,HandlerInterceptor為Spring MVC中的攔截器,
* 可以在Controller方法執行之前之后執行一些動作.
* 1)Handler 處理器(Spring MVC中將@RestController描述的類看成是處理器)
* 2)Interceptor 攔截器
*/
public class TokenInterceptor implements HandlerInterceptor {
/**
* preHandle在目標Controller方法執行之前執行
* @param handler 目標Controller物件
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//http://localhost:8080/retrieve?Authentication=
//String token= request.getParameter("Authentication");
String token=request.getHeader("Authentication");
//判定請求中是否有令牌
if(token==null||"".equals(token))
throw new RuntimeException("please login");
//判定令牌是否已經過期
boolean flag=JwtUtils.isTokenExpired(token);
if(flag)
throw new RuntimeException("login timeout,please login");
return true;//true表示放行,false表示攔截到請求以后,不再繼續傳遞
}
}
攔截器撰寫好以后,需要將攔截器添加到spring mvc執行鏈中并設定要攔截的請求,可通過配置類完成這個程序,代碼如下:
package com.cy.jt.security.config;
/**
* 定義Spring Web MVC 配置類
*/
@Configuration
public class SpringWebConfig implements WebMvcConfigurer {
/**將攔截器添加到spring mvc的執行鏈中
* @param registry 此物件提供了一個list集合,可以將攔截器添加到集合中
* */
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor())
//配置要攔截的url
.addPathPatterns("/retrieve","/update");
}
}
Postman訪問測驗
第一步:登錄訪問測驗

第二步:進行資源訪問測驗,例如:

總結(Summary)
本章節重點講解了JWT產生的背景,它的構成和專案中的基本應用,需要在實踐中進行分析和理解.
重難點分析
- JWT 誕生的背景?(分布式架構應用平臺下無狀態會話時,規范令牌(通票)資料格式)
- JWT 規范定義的資料格式?(頭,負載-詳細內容,簽名,思考一篇文章的構成,)
- JWT 規范下JAVA相關API的應用?(jjwt依賴-Jwts)
- 基于JWT規范下JAVA API 創建令牌,決議令牌
FAQ分析
- JWT 是什么?(一種規范的資料格式)
- JWT規范中的資料格式有幾部分構成?(3部分,前兩部分會進行Base64編碼,最會基于簽名演算法加密)
- JWT的負載(Payload-存盤實際用戶資訊的部分)部分可以自定義嗎?(Claims)
- JWT令牌物件一般是在哪里創建?(服務端,可以創建令牌以后,回應到客戶端)
- JWT令牌假如要存盤在客戶端你會存盤在哪里?(Cookie,localStorage,sessionStorage)
- JWT令牌以怎樣的方式有客戶端傳遞到服務端?(請求引數,請求頭)
Bug分析
- 創建token和決議token時一定要相同的密鑰
- Token過期了
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/289752.html
標籤:其他
上一篇:HTTPS原理
下一篇:面試大廠,我是這樣準備專案的
