4. JWT令牌原理与生成
JWT(JSON Web Token)是权限审计网关中最常用的令牌格式。它通过自包含的声明结构,在客户端与服务端之间安全地传递用户身份与权限信息。本章将深入解析JWT的结构、签名算法,并演示如何在Go中生成与验证JWT。
4.1 JWT结构:Header / Payload / Signature
一个JWT由三个部分组成,以点号(.)分隔:Header.Payload.Signature。每个部分都是Base64URL编码的JSON字符串。
| 部分 | 名称 | 内容 | 是否加密 |
|---|---|---|---|
| Header | 头部 | 令牌类型(typ)和签名算法(alg) | 仅Base64编码,不加密 |
| Payload | 载荷 | 声明(Claims):用户ID、角色、过期时间等 | 仅Base64编码,不加密 |
| Signature | 签名 | 对Header和Payload的签名,防止篡改 | 由密钥或私钥生成 |
重要提示:JWT默认只签名不加密。Payload中的敏感信息(如密码、身份证号)不应直接放入JWT,除非使用JWE(JSON Web Encryption)进行加密。
4.1.1 Header(头部)
Header通常包含两个字段:
alg:签名算法,如HS256、RS256、ES256typ:令牌类型,固定为JWT
{
"alg": "RS256",
"typ": "JWT"
}
4.1.2 Payload(载荷)
Payload包含声明(Claims),分为三类:
- 注册声明(Registered Claims):预定义的标准字段,如
iss(签发者)、sub(主题)、exp(过期时间)、iat(签发时间) - 公共声明(Public Claims):自定义字段,需在IANA注册或使用命名空间避免冲突
- 私有声明(Private Claims):双方约定的自定义字段,如
role、permissions
{
"sub": "1234567890",
"name": "张三",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
4.1.3 Signature(签名)
签名由以下公式生成:
signature = HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
对于RS256算法,签名使用私钥进行RSA签名,验证时使用公钥。
4.2 RS256 与 HS256 算法对比
HS256(HMAC with SHA-256)和 RS256(RSA with SHA-256)是最常用的两种JWT签名算法。它们在密钥管理、安全性和性能上有显著差异。
| 对比维度 | HS256(对称) | RS256(非对称) |
|---|---|---|
| 密钥类型 | 单一共享密钥 | 私钥签名 + 公钥验证 |
| 密钥分发 | 需安全通道共享密钥 | 公钥可公开分发 |
| 签名速度 | 快 | 慢(私钥签名) |
| 验证速度 | 快 | 快(公钥验证) |
| 安全性风险 | 密钥泄露可伪造任意令牌 | 私钥泄露风险低,公钥泄露不影响 |
| 适用场景 | 单体应用、内部服务 | 微服务、第三方认证、分布式系统 |
安全警告:在微服务架构中,强烈建议使用RS256。每个服务只需持有公钥即可验证令牌,无需共享密钥。如果使用HS256,任何一个服务被攻破,整个系统的令牌信任体系都会崩溃。
4.3 Go中生成JWT
Go生态中最流行的JWT库是 github.com/golang-jwt/jwt/v5。下面分别演示HS256和RS256的令牌生成。
4.3.1 使用HS256生成JWT
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
func main() {
// 1. 定义共享密钥
secret := []byte("your-256-bit-secret-key-here")
// 2. 创建声明(Claims)
claims := jwt.MapClaims{
"sub": "user-001",
"name": "张三",
"role": "admin",
"iat": time.Now().Unix(),
"exp": time.Now().Add(2 * time.Hour).Unix(),
}
// 3. 创建令牌
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 4. 签名生成完整JWT字符串
tokenString, err := token.SignedString(secret)
if err != nil {
panic(err)
}
fmt.Println("HS256 JWT:", tokenString)
}
4.3.2 使用RS256生成JWT
RS256需要先生成RSA密钥对。可以使用OpenSSL生成,也可以在Go中动态生成。
package main
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
func main() {
// 1. 生成RSA密钥对(生产环境应使用持久化密钥)
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
// 2. 创建声明
claims := jwt.MapClaims{
"sub": "user-001",
"name": "张三",
"role": "admin",
"iat": time.Now().Unix(),
"exp": time.Now().Add(2 * time.Hour).Unix(),
}
// 3. 使用RS256签名方法创建令牌
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
// 4. 使用私钥签名
tokenString, err := token.SignedString(privateKey)
if err != nil {
panic(err)
}
fmt.Println("RS256 JWT:", tokenString)
// 5. 公钥用于验证(可安全分发给其他服务)
publicKey := &privateKey.PublicKey
_ = publicKey // 实际使用中传递给验证函数
}
4.3.3 验证JWT令牌
func verifyJWT(tokenString string, key interface{}) (*jwt.Token, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// 验证签名算法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
// HS256:返回共享密钥
return []byte("your-256-bit-secret-key-here"), nil
}
if _, ok := token.Method.(*jwt.SigningMethodRSA); ok {
// RS256:返回公钥
return publicKey, nil
}
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
})
if err != nil {
return nil, err
}
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
return token, nil
}
最佳实践:
- 始终在验证函数中检查
token.Method,防止算法混淆攻击(攻击者将RS256改为HS256并用公钥签名) - 设置合理的
exp(过期时间),建议15分钟到2小时 - 使用
jwt.NewParser()并设置WithValidMethods()明确指定允许的算法
4.4 令牌生命周期与安全策略
| 阶段 | 操作 | 说明 |
|---|---|---|
| 签发 | 用户登录成功后生成JWT | 包含用户ID、角色、过期时间 |
| 传输 | 通过HTTP Header: Authorization: Bearer <token> |
必须使用HTTPS防止中间人攻击 |
| 验证 | 网关或服务端解析并验证签名 | 检查签名、过期时间、签发者 |
| 刷新 | 使用Refresh Token获取新的Access Token | 避免频繁登录,同时限制Access Token有效期 |
| 撤销 | 加入黑名单或使用短有效期 | JWT本身无状态,撤销需额外机制 |
4.5 本章小结
- JWT由Header、Payload、Signature三部分组成,以点号分隔
- HS256使用共享密钥,适合单体应用;RS256使用公私钥对,适合分布式系统
- Go中使用
golang-jwt/jwt/v5库可以方便地生成和验证JWT - 验证时必须检查签名算法,防止算法混淆攻击
- JWT本身无状态,撤销需要额外的黑名单或短有效期策略