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

journey
32 声望22 粉丝