1、JWT簡介
JWT (JSON Web Token) 是一種基於 JSON 格式的開放標準(RFC 7519),用於在不同系統間作為一種安全的、緊湊的令牌實現信息的傳遞。它通常用於身份驗證、授權以及信息安全傳遞
1.1、JWT 的組成
JWT 包含三個部分,每部分用 . 分隔:
Header.Payload.Signature
1、Header(頭部)
Header 通常包含兩部分信息:
- 類型(typ):JWT
- 簽名算法(alg):例如 HMAC、SHA256 或 RSA
示例 :
{
"alg": "HS256",
"typ": "JWT"
}
經過 Base64Url 編碼後:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2、Payload(載荷)
Payload 是存儲實際數據的部分,可以包含用户信息或聲明(Claims)。通常包含以下兩類聲明:
- Registered Claims(預定義聲明):如 iss(簽發者)、exp(過期時間)、sub(主題)、aud(受眾)等
- Public Claims(公共聲明):開放給所有用户使用,但需避免衝突
- Private Claims(私有聲明):為雙方協商使用
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
經過 Base64Url 編碼後:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3、Signature(簽名)
簽名是通過將 Header 和 Payload 的 Base64 編碼部分連接起來,用指定的算法(如 HMAC SHA256)與密鑰進行加密生成的
生成簽名的公式:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
1.2、JWT 的工作原理
1、客户端登錄:
用户向服務器發送登錄請求,驗證成功後,服務器生成 JWT,發送給客户端。
2、客户端存儲 Token:
客户端通常將 Token 存儲在 Cookie 或 LocalStorage 中。
3、請求攜帶 Token:
客户端每次請求 API 時,將 JWT 放在請求頭的 Authorization 中,例如:
Authorization: Bearer <JWT_TOKEN>
4、服務器驗證 Token:
服務器通過解析 JWT 驗證簽名是否正確,以及 Token 是否過期
1.3、JWT 優缺點
優點 :
1、自包含性:
JWT 包含了所有必要信息,無需額外的數據庫查詢
2、輕量和跨語言支持:
JWT 是一個緊湊的字符串,傳輸開銷小
各種語言都有庫支持(如 Go、Java、Python、Node.js)
3、易於擴展:
可以在 Payload 中存儲自定義數據
缺點 :
1、無法撤銷:
如果 JWT 未存儲在服務器端,一旦簽發無法輕易撤銷
2、體積可能較大:
如果 Payload 數據過多,傳輸體積會增加
3、安全風險:
密鑰一旦泄露,JWT 的簽名將不再可信
必須小心管理 secret 並限制 Payload 的敏感數據
1.4、JWT 的應用場景
1、身份驗證
用户登錄後,JWT 可用作會話標識,客户端每次請求時攜帶 JWT 作為用户身份
2、信息交換
JWT 的簽名可以確保信息的完整性,因此它也可以用於不同系統之間安全傳遞信息
3、單點登錄(SSO)
JWT 可以在不同系統之間安全地傳遞用户身份,適用於 SSO 場景
2、go JWT 示例
2.1、安裝依賴
go get github.com/golang-jwt/jwt/v4
2.2、生成和解析 JWT 的完整代碼
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
)
// 定義 JWT 的密鑰
var jwtKey = []byte("my_secret_key")
// 自定義的 Claims 結構
type Claims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
// 生成 JWT
func generateJWT(username string) (string, error) {
// 設置過期時間
expirationTime := time.Now().Add(5 * time.Minute)
// 創建自定義的 Claims
claims := &Claims{
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
// 創建 token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 使用密鑰簽名
tokenString, err := token.SignedString(jwtKey)
if err != nil {
return "", err
}
return tokenString, nil
}
// 驗證 JWT
func parseJWT(tokenString string) (*Claims, error) {
claims := &Claims{}
// 解析 token
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
// 驗證使用的是 HMAC 的簽名方法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return jwtKey, nil
})
if err != nil {
return nil, err
}
// 檢查 token 是否有效
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
return claims, nil
}
func main() {
// 生成一個 JWT
token, err := generateJWT("testuser")
if err != nil {
fmt.Println("Error generating token:", err)
return
}
fmt.Println("Generated Token:", token)
// 解析並驗證 JWT
claims, err := parseJWT(token)
if err != nil {
fmt.Println("Error parsing token:", err)
return
}
fmt.Printf("Token is valid! Username: %s, ExpiresAt: %s\n", claims.Username, claims.ExpiresAt.Time)
}
運行結果 :
Generated Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3R1c2VyIiwiZXhwIjoxNzMzMjIzMzEzfQ.XMFme_R3lSicA_4nATbx_wEMjJYbS6BhkEdECRVEsLc
Token is valid! Username: testuser, ExpiresAt: 2024-12-03 18:55:13 +0800 CST
3、java JWT 示例
3.1、pom 依賴
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
3.2、完整代碼
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class Demo {
public static void main(String[] args) throws Exception {
// 1. 定義密鑰
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256); // 生成隨機密鑰
// 2. 生成 JWT
String jwt = generateJwt(secretKey);
System.out.println("生成的 JWT: " + jwt);
// 3. 驗證 JWT
JwtResult jwtResult = validateJwt(jwt, secretKey);
System.out.println("JWT 是否有效: " + jwtResult.valid);
if (jwtResult.valid) {
Claims claims = jwtResult.getClaims();
String userId = claims.get("userId").toString();
String username = claims.get("username").toString();
Boolean isAdmin = Boolean.getBoolean(claims.get("admin").toString());
System.out.println("userId :" + userId + "; username :" + username + "; isAdmin :" + isAdmin + "; expiration : " + claims.getExpiration());
}
}
// 生成 JWT 的方法
public static String generateJwt(SecretKey secretKey) {
// 自定義 Payload 數據
Map<String, Object> claims = new HashMap<>();
claims.put("userId", "12345");
claims.put("username", "john_doe");
claims.put("admin", true);
// 生成 JWT
return Jwts.builder()
.setClaims(claims) // 添加自定義聲明
.setSubject("User Authentication") // 設置主題
.setIssuer("MyApp") // 設置簽發者
.setIssuedAt(new Date()) // 設置簽發時間
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 設置過期時間 (1小時)
.signWith(secretKey) // 使用密鑰簽名
.compact();
}
// 驗證 JWT 的方法
public static JwtResult validateJwt(String jwt, SecretKey secretKey) {
try {
// 解析 JWT
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(secretKey) // 設置密鑰
.build()
.parseClaimsJws(jwt);
return new JwtResult(true, claimsJws.getBody());
} catch (Exception e) {
System.out.println("JWT 驗證失敗: " + e.getMessage());
return new JwtResult(false ,null);
}
}
static class JwtResult {
private Boolean valid;
private Claims claims;
public JwtResult(Boolean valid, Claims claims) {
this.valid = valid;
this.claims = claims;
}
public Boolean getValid() {
return valid;
}
public void setValid(Boolean valid) {
this.valid = valid;
}
public Claims getClaims() {
return claims;
}
public void setClaims(Claims claims) {
this.claims = claims;
}
}
}
運行結果 :
生成的 JWT: eyJhbGciOiJIUzI1NiJ9.eyJhZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTIzNDUiLCJ1c2VybmFtZSI6ImpvaG5fZG9lIiwic3ViIjoiVXNlciBBdXRoZW50aWNhdGlvbiIsImlzcyI6Ik15QXBwIiwiaWF0IjoxNzMzMjIzMTk1LCJleHAiOjE3MzMyMjY3OTV9.3npD-fv37fGlP573GSAG6OvqQ9m5cbBFFufFz75lXRY
JWT 是否有效: true
userId :12345; username :john_doe; isAdmin :false; expiration : Tue Dec 03 19:53:15 CST 2024