本章節討論 jwt 在 spring boot 中的應用。意在快速入門 jwt。
- java jdk1.8
- maven 3.2+
- spring boot 2.0+
JSON Web Token(JWT) 他是一個用於 Web 身份驗證的令牌。
1 JWT 概述
1.1 什么是JWT
直觀的理解 JWT 就是一串字符串,如下(來自於 JWT.IO):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
細心的你可能會發現字符串是有3個獨立的字符串使用.
號組合而成,前兩個字符串是 Base64 編碼,最后一個字符串是一個加密后的字符串。
序號 | 字符串 |
---|---|
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
2 | eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ |
3 | SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
JWT 使用以上3個字符串組成一個字符串,以 xxxxx.yyyyyy.zzzzz
的形式傳輸給認證服務器
xxxxx.yyyyyy.zzzzz
表示為 Header.Payload.Signature
Header=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload=eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Signature=SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
我們使用 JWT Debuger 把信息解碼可以看出:
1.1.1 Header
Header 頭信息,以 json 字符串實現,並壓縮成 Base64URL
編碼字符串傳輸
{
"alg": "HS256",
"typ": "JWT"
}
上面代碼中,alg屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256);typ屬性表示這個令牌(token)的類型(type),JWT 令牌統一寫為JWT。
1.1.2 Payload
Payload 負載,以 json 字符串實現,並壓縮成 Base64編碼字符串傳輸
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
官方對 json 內容節點給出了一些建議,我個人覺得意義不大,況且那個建議字段完全是看不出任何含義的縮寫。比如 iss、exp、sub、aud、nbf、iat、jti
。當然如果大家都能共同遵守,也不存在這些問題。
iss (issuer):簽發人
exp (expiration time):過期時間
sub (subject):主題
aud (audience):受眾
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號
1.1.3 Signature
VERIFY SIGNATURE 簽名,在服務端指定一個秘鑰,把簽名兩個 json 字符串使用下面方法加密而成的
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
only-save-in-server-secret)
值得注意的是,使用JWT 推薦使用 HTTPS 加密協議
1.1.4 Base64URL
BASE64URL是一種在BASE64的基礎上編碼形成新的加密方式,為了編碼能在網絡中安全順暢傳輸,需要對BASE64進行的編碼,特別是互聯網中。
1.2 JWT 是如何工作的
為了能夠支持跨域,通常 JWT 的字符串 xxxxx.yyyyyy.zzzzz
通過 Http 的 Head 發送
Authorization: Bearer <token>
也可以放在 Post 數據體重。
2 Spring Boot JWT 示例
在 JWT 官方可以看到,maven: io.jsonwebtoken / jjwt / 0.9.0
這個庫是支持最全的。
2.1 新建 Spring Boot Maven 示例工程項目
- File > New > Project,如下圖選擇
Spring Initializr
然后點擊 【Next】下一步 - 填寫
GroupId
(包名)、Artifact
(項目名) 即可。點擊 下一步
groupId=com.fishpro
artifactId=jwt - 選擇依賴
Spring Web Starter
前面打鈎。 - 項目名設置為
spring-boot-study-jwt
.
2.2 依賴引入 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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.fishpro</groupId>
<artifactId>jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jwt</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.4 編寫 JWT 生成與驗證代碼
本章節知識學習 JWT,不涉及其他知識點,故我們之間在 JwtApplication.java 中來編寫代碼
package com.fishpro.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.*;
@SpringBootApplication
public class JwtApplication {
public static void main(String[] args) {
SpringApplication.run(JwtApplication.class, args);
String token=createJWTToken();//獲取生成的Token
verifyJWTToken(token);//驗證生成的Token
}
/**
* 生成 JWT Token
* */
private static String createJWTToken() throws JWTCreationException {
String secret="secret";//假設服務端秘鑰
Algorithm algorithm = Algorithm.HMAC256(secret);
//jwt 頭部信息
Map<String,Object> map=new HashMap<>();
map.put("alg","HS256");
map.put("typ","JWT");
Date nowDate = new Date();
Date expireDate = AddDate(nowDate,2*60);//120 分鍾過期
String token= JWT.create()
.withHeader(map)
.withIssuer("SERVICE") //對應 paylaod iss 節點:簽發人
.withClaim("loginName","fishpro")
.withSubject("this is a token demo")//對應 paylaod sub 節點:主題
.withAudience("Client")//對應 paylaod aud 節點:受眾
.withIssuedAt(nowDate)//對應 paylaod iat 節點:生效時間
.withExpiresAt(expireDate) //對應 paylaod exp 簽發人 節點:過期時間
.sign(algorithm);
return token;
}
/**
* 驗證 token
* */
private static void verifyJWTToken(String token) throws JWTVerificationException {
Algorithm algorithm=Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("SERVICE")
.build();
DecodedJWT jwt =verifier.verify(token);
String subject=jwt.getSubject();
Map<String,Claim> claims=jwt.getClaims();
Claim claim = claims.get("loginName");
System.out.println("自定義 claim:"+claim.asString());
List<String> audience = jwt.getAudience();
System.out.println("subject 值:"+subject);
System.out.println("audience 值:"+audience.get(0));
}
/**
* 時間加減法
* */
private static Date AddDate(Date date,Integer minute){
if(null==date)
date=new Date();
Calendar cal=new GregorianCalendar();
cal.setTime(date);
cal.add(Calendar.MINUTE, minute);
return cal.getTime();
}
}
2.5 演示效果
右鍵 JwtApplication 選擇 Run JwtApplication ,觀察 console 窗口