一文读懂 JWT(JSON Web Token)
🔐 一文读懂 JWT(JSON Web Token)
一、什么是 JWT?
- JWT(JSON Web Token)
一种基于 JSON 的开放标准(RFC 7519),用于在各方之间安全地传递信息。 - 特点
- 紧凑(Compact):可通过 URL、POST 参数或 HTTP 头传输
- 自包含(Self-contained):携带了用户的基本身份信息和声明(Claims)
- 可验证(Verifiable):签名保证数据未被篡改
📌 典型场景:用户登录后,服务器颁发一个 JWT,客户端每次请求都携带它,无需再查数据库做 Session 验证。
二、JWT 的三部分结构
一个典型的 JWT 长这样:
xxxxxxx.yyyyyyy.zzzzzzz
部分 | 用途 | 示例内容 |
---|---|---|
Header(头部) | 指定算法和类型 | {"alg":"HS256","typ":"JWT"} |
Payload(负载) | 存放声明(Claims),如用户 ID、过期时间等 | {"sub":"1234567890","name":"Alice","exp":1600000000} |
Signature(签名) | 保证前两部分不被篡改 | HMACSHA256(Base64Url(Header) + "." + Base64Url(Payload), Secret) |
✨ 注意:Header 和 Payload 都要做 Base64Url 编码!
JWT = Base64Url(Header) + "." + Base64Url(Payload) + "." + Base64Url(Signature)
三、JWT Secret(密钥)介绍 🔑
- 作用
- 对称签名(HS256/HS384/HS512)中,Secret 用来签发和验证 Token。
- 形式 & 长度
- 高强度随机字节,建议 ≥256 bits(32 bytes)
- 常见编码:
- URL-Safe Base64
9d6bXFMmZ3RV8Ytp9rz8QpKBuGV9zZ4T5vHSuJEjw8M
- Hex
e75ab5c53266774557c62da7dacfc429281b8695f736784f9bc74ae24923c3c30
- URL-Safe Base64
- 如何生成?
- Go 代码示例:
import ("crypto/rand""encoding/base64""log" )func generateSecret() string {b := make([]byte, 32)if _, err := rand.Read(b); err != nil {log.Fatalf("生成 Secret 失败:%v", err)}// RawURLEncoding 去掉末尾 = 号,更 URL-Safereturn base64.RawURLEncoding.EncodeToString(b) }
- Go 代码示例:
- 泄露后果
- 攻击者可伪造任意合法 JWT,冒充用户或管理员
- 权限绕过、数据篡改、系统信任链崩溃
- 等同「根密钥」被盗,所有鉴权瞬间失效
- 防护 & 补救
- 安全存储:✨使用 Vault、AWS KMS、GCP Secret Manager 等
- 定期轮换(Key Rotation):支持「新」+「旧」秘钥平滑过渡
- 缩短生命周期:结合
exp
,让 Token & Secret 都有“有效期” - 多重验证:对支付、重置密码等关键操作再做一次二次校验
- 监控告警:异常签名、同账户多地登录及时告警并强制登出
四、签名算法(alg)
算法名称 | 描述 |
---|---|
HS256 | HMAC + SHA-256,对称加密(Shared Secret) |
HS384 | HMAC + SHA-384 |
HS512 | HMAC + SHA-512 |
RS256 | RSA + SHA-256,非对称加密(公钥/私钥对) |
ES256 | ECDSA + SHA-256,椭圆曲线数字签名算法 |
小项目常用 HS256;高安全需求可选 RS256(私钥签发、公钥验签)。
五、JWT 的工作流程
- 用户登录(提供用户名/密码)
- 服务器验证成功后,签发 JWT
- 客户端保存(LocalStorage / Cookie)
- 后续请求携带 JWT
- 推荐:请求头
Authorization: Bearer <token>
- 推荐:请求头
- 服务器 验证签名 & 检查声明(如是否过期、是否有权限)
- 验证通过,返回数据;否则
401 Unauthorized
六、签名 & 验证(HS256 示例)
1. 签名生成
令
H = Base64Url(Header)
P = Base64Url(Payload)
Secret = 服务器持有的密钥
LaTeX 公式版:
Signature=HMACSHA256(H+"."+P, Secret) \mathrm{Signature} = \mathrm{HMACSHA256}\bigl(H + "." + P,\ \mathrm{Secret}\bigr) Signature=HMACSHA256(H+"."+P, Secret)
2. 验证流程
- 拆分
Header.Payload.Signature
- 重新计算
HMACSHA256(H+"."+P, Secret)
- 对比结果:
- 相同:✅ 数据未被篡改
- 不同:❌ 拒绝访问
七、优缺点一览
优点 | 缺点 |
---|---|
1. 无状态(Stateless),可水平扩展 🌐 | 1. 无法即时「撤销」已签发的 Token |
2. 携带信息自包含,无需多次查询数据库 📦 | 2. Token 泄露风险大,需妥善存储 |
3. 支持跨域认证(适合微服务、移动端)📱🌍 | 3. Payload 明文可读,敏感信息请勿存放 |
八、实战示例(Go 语言版)
下面用 Go + 标准库 + github.com/golang-jwt/jwt/v4
库演示:
package mainimport ("crypto/rand""encoding/base64""fmt""log""net/http""strings""time""github.com/golang-jwt/jwt/v4"
)// 1. 生成 Secret
func generateSecret() string {b := make([]byte, 32)if _, err := rand.Read(b); err != nil {log.Fatalf("生成 Secret 失败:%v", err)}return base64.RawURLEncoding.EncodeToString(b)
}// 全局 Secret(示例中硬编码,生产环境请用安全存储)
var SECRET = generateSecret()// 2. 签发 JWT
func createToken(username string) (string, error) {claims := jwt.MapClaims{"sub": username,"name": "Alice","iat": time.Now().Unix(),"exp": time.Now().Add(time.Hour).Unix(),}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)return token.SignedString([]byte(SECRET))
}// 3. 验证 JWT
func verifyToken(tokenStr string) (*jwt.Token, error) {return jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])}return []byte(SECRET), nil})
}func main() {http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {// 仅示例:实际应解析 JSON & 验证数据库username, password := r.URL.Query().Get("user"), r.URL.Query().Get("pass")if username == "alice" && password == "123" {token, err := createToken(username)if err != nil {http.Error(w, "Token 签发失败", 500)return}fmt.Fprintf(w, `{"token":"%s"}`, token)return}http.Error(w, "用户名或密码错误", 401)})http.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {auth := r.Header.Get("Authorization")parts := strings.SplitN(auth, " ", 2)if len(parts) != 2 || parts[0] != "Bearer" {http.Error(w, "缺少或无效的 Authorization 头", 401)return}token, err := verifyToken(parts[1])if err != nil || !token.Valid {http.Error(w, "Token 无效或已过期", 401)return}claims := token.Claims.(jwt.MapClaims)fmt.Fprintf(w, `{"user":"%s","name":"%s"}`, claims["sub"], claims["name"])})log.Printf("🔑 Secret(演示用): %s\n", SECRET)log.Println("🚀 Server 启动: http://localhost:8080")log.Fatal(http.ListenAndServe(":8080", nil))
}
启动后:
- 登录:
GET http://localhost:8080/login?user=alice&pass=123 - 拿到
{"token":"..."}
- 请求受保护路由:
GET /profile HTTP/1.1 Host: localhost:8080 Authorization: Bearer <token>
九、总结 & 小贴士
- Secret 存储
- 生产环境切勿硬编码,使用专业密钥管理服务(Vault, AWS KMS…)
- Token 生命周期
- 设置合理
exp
,并对关键操作做二次校验
- 设置合理
- 安全细节
- Payload 明文可读,切勿存敏感数据
- 定期轮换 Secret,缩小泄露风险窗口
- 始终启用 HTTPS,防止中间人攻击
🎉 至此,你已经掌握了 JWT 的核心原理、结构、Secret 要点、使用流程及 Go 实战示例。快去项目中试一试吧!