相关文章:
想要学习 golang-jwt(通常指 github.com/golang-jwt/jwt/v5,是原 github.com/dgrijalva/jwt-go 的官方维护续版)的使用,本教程将从环境准备、JWT 生成、验证、核心特性等方面完整讲解,确保你能快速上手。
一、环境准备
1. 安装 golang-jwt 包
首先在你的 Go 项目中安装 jwt/v5 版本(推荐使用 v5 稳定版,兼容 Go 1.16+ 模块机制):
go get github.com/golang-jwt/jwt/v5
2. 核心概念说明
JWT 由三部分组成:Header(头部)、Payload(载荷)、Signature(签名),golang-jwt 核心完成签名生成和签名验证两大核心功能,其中:
- 密钥(Secret/Key)是关键:对称加密使用单一密钥,非对称加密使用公钥 / 私钥对
- 载荷包含标准声明(如过期时间、发行人)和自定义声明(业务数据)
二、对称加密(HS256/HS512)使用(最常用)
对称加密使用同一把密钥进行签名和验证,操作简单、性能高,适合服务内部的 JWT 生成与验证。
步骤 1:定义核心常量
package main
import (
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// 1. 定义对称加密密钥(生产环境请存储在环境变量/配置中心,不要硬编码)
const secretKey = "your-strong-secret-key-32bytes-long-recommend" // 推荐使用 32/64 字节强密钥
// 2. 定义自定义载荷结构体(嵌入 jwt.RegisteredClaims 以支持标准声明)
type CustomClaims struct {
// 自定义业务字段(根据需求添加)
UserID uint64 `json:"user_id"`
Username string `json:"username"`
// 嵌入标准声明(必须,v5 版本强制要求使用 RegisteredClaims 管理标准字段)
jwt.RegisteredClaims
}
步骤 2:生成 JWT Token
核心流程:
- 构造自定义载荷(包含标准声明和业务字段)
- 创建 JWT 令牌对象,指定签名算法(HS256)
- 使用密钥进行签名,生成字符串格式的 Token
// GenerateToken 生成对称加密的 JWT Token
func GenerateToken(userID uint64, username string) (string, error) {
// 1. 设置令牌过期时间(推荐设置,避免 Token 永久有效)
expireTime := time.Now().Add(2 * time.Hour) // 2 小时过期
// 2. 构造自定义载荷
claims := CustomClaims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
// 标准声明字段(按需设置)
ExpiresAt: jwt.NewNumericDate(expireTime), // 过期时间(必填,核心验证字段)
IssuedAt: jwt.NewNumericDate(time.Now()), // 签发时间
NotBefore: jwt.NewNumericDate(time.Now()), // 生效时间(立即生效)
Issuer: "your-app-name", // 签发人(应用标识)
Subject: fmt.Sprintf("%d", userID), // 主题(通常为用户唯一标识)
},
}
// 3. 创建 JWT 令牌对象,指定签名算法和载荷
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 4. 使用密钥签名,生成 Token 字符串
tokenString, err := token.SignedString([]byte(secretKey))
if err != nil {
return "", fmt.Errorf("生成 Token 失败:%w", err)
}
return tokenString, nil
}
步骤 3:验证 JWT Token 并解析载荷
核心流程:
- 解析 Token 字符串,指定验证密钥和声明类型
- 验证 Token 的签名有效性、过期时间等
- 提取验证通过后的自定义载荷数据
// ParseToken 验证并解析 JWT Token
func ParseToken(tokenString string) (*CustomClaims, error) {
// 1. 解析 Token(第二个参数为密钥获取函数,返回验证用的密钥)
token, err := jwt.ParseWithClaims(
tokenString,
&CustomClaims{}, // 指定要解析到的自定义声明类型
func(token *jwt.Token) (interface{}, error) {
// 验证签名算法是否与预期一致(防止算法篡改攻击)
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("不支持的签名算法:%v", token.Header["alg"])
}
// 返回对称加密密钥
return []byte(secretKey), nil
},
)
if err != nil {
return nil, fmt.Errorf("解析 Token 失败:%w", err)
}
// 2. 验证 Token 是否有效(签名有效、未过期等)
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
// 3. 返回解析后的自定义载荷
return claims, nil
}
return nil, errors.New("无效的 Token")
}
步骤 4:测试使用
func main() {
// 1. 生成 Token
token, err := GenerateToken(1001, "zhangsan")
if err != nil {
fmt.Printf("生成 Token 出错:%v\n", err)
return
}
fmt.Printf("生成的 JWT Token:\n%s\n\n", token)
// 2. 验证并解析 Token
claims, err := ParseToken(token)
if err != nil {
fmt.Printf("解析 Token 出错:%v\n", err)
return
}
// 3. 打印解析后的载荷数据
fmt.Printf("解析成功,用户 ID:%d\n", claims.UserID)
fmt.Printf("解析成功,用户名:%s\n", claims.Username)
fmt.Printf("解析成功,过期时间:%v\n", claims.ExpiresAt.Time)
fmt.Printf("解析成功,签发人:%s\n", claims.Issuer)
}
三、非对称加密(RS256)使用(适合跨服务场景)
非对称加密使用「私钥签名、公钥验证」,适合多服务协作场景(如授权中心生成 Token,其他服务仅用公钥验证,无需暴露私钥)。
步骤 1:生成 RSA 密钥对
首先通过命令行生成 RSA 私钥和公钥:
# 生成 RSA 私钥(2048 位,保存为 private.pem) openssl genrsa -out private.pem 2048 # 从私钥提取公钥(保存为 public.pem) openssl rsa -in private.pem -pubout -out public.pem
步骤 2:私钥生成 Token
// GenerateRSAToken 使用 RSA 私钥生成 Token
func GenerateRSAToken(userID uint64, username string) (string, error) {
// 1. 读取 RSA 私钥文件
privateKeyBytes, err := os.ReadFile("private.pem")
if err != nil {
return "", fmt.Errorf("读取私钥失败:%w", err)
}
// 2. 解析 RSA 私钥
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
if err != nil {
return "", fmt.Errorf("解析私钥失败:%w", err)
}
// 3. 构造载荷(与对称加密一致)
expireTime := time.Now().Add(2 * time.Hour)
claims := CustomClaims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expireTime),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "your-app-name",
},
}
// 4. 指定 RS256 算法,创建 Token 并签名
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
tokenString, err := token.SignedString(privateKey)
if err != nil {
return "", fmt.Errorf("RSA 签名 Token 失败:%w", err)
}
return tokenString, nil
}
步骤 3:公钥验证 Token
// ParseRSAToken 使用 RSA 公钥验证并解析 Token
func ParseRSAToken(tokenString string) (*CustomClaims, error) {
// 1. 读取 RSA 公钥文件
publicKeyBytes, err := os.ReadFile("public.pem")
if err != nil {
return "", fmt.Errorf("读取公钥失败:%w", err)
}
// 2. 解析 RSA 公钥
publicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKeyBytes)
if err != nil {
return "", fmt.Errorf("解析公钥失败:%w", err)
}
// 3. 解析并验证 Token
token, err := jwt.ParseWithClaims(
tokenString,
&CustomClaims{},
func(token *jwt.Token) (interface{}, error) {
// 验证签名算法为 RS256
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("不支持的签名算法:%v", token.Header["alg"])
}
return publicKey, nil
},
)
if err != nil {
return nil, fmt.Errorf("解析 RSA Token 失败:%w", err)
}
// 4. 返回有效载荷
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("无效的 RSA Token")
}
四、核心注意事项(生产环境必备)
- 密钥安全:
- 对称加密密钥不要硬编码,应存储在环境变量(如
os.Getenv("JWT_SECRET"))、配置中心(如 Nacos、Consul)或加密存储 - RSA 私钥严格保密,避免泄露,公钥可公开分发
- 设置合理过期时间:
- 避免 Token 永久有效,推荐设置 1-24 小时过期,短期操作可设置更短时间
- 如需长期有效,可配合「刷新令牌(Refresh Token)」机制
- 验证签名算法:
- 解析 Token 时必须验证算法是否与预期一致,防止「算法篡改攻击」(如将 HS256 改为 none 绕过验证)
- 自定义声明不存敏感数据:
- JWT 载荷仅做 Base64 编码(可轻松解码),不要存储密码、身份证等敏感信息
- 错误处理:
- 生产环境避免返回详细的 JWT 错误信息给前端,防止泄露系统细节
- 版本选择:
- 始终使用
github.com/golang-jwt/jwt/v5,原dgrijalva/jwt-go已停止维护,存在安全风险
五、运行结果示例
生成的 JWT Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMDAxLCJ1c2VybmFtZSI6InpoYW5nc2FuIiwiZXhwIjoxNzM1NjQyNjYwLCJpYXQiOjE3MzU2MzU0NjAsIm5iZiI6MTczNTYzNTQ2MCwiaXNzIjoieW91ci1hcHAtbmFtZSIsInN1YiI6IjEwMDEifQ.xxxxxxxxx(签名部分) 解析成功,用户 ID:1001 解析成功,用户名:zhangsan 解析成功,过期时间:2025-12-28 18:17:40 +0800 CST 解析成功,签发人:your-app-name
总结
golang-jwt核心使用jwt.NewWithClaims()生成 Token,jwt.ParseWithClaims()验证解析 Token- 对称加密(HS256)简单高效,适合单服务场景;非对称加密(RS256)更安全,适合跨服务场景
- 自定义载荷必须嵌入
jwt.RegisteredClaims,以支持标准过期时间等核心验证 - 生产环境重点关注密钥安全、过期时间设置和算法验证,避免安全漏洞