动态

详情 返回 返回

JWT入門 - 动态 详情

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
user avatar u_16502039 头像 u_15702012 头像 chuanghongdengdeqingwa_eoxet2 头像 lvlaotou 头像 lenve 头像 nianqingyouweidenangua 头像 lvweifu 头像 pottercoding 头像 mecode 头像 jacklv 头像 cumeimaodeyingpan 头像 nathannie 头像
点赞 22 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.