Go 认证与 JWT
Go 服务中基于 JWT 的认证实现,涵盖 Token 结构、创建解析、刷新机制、中间件集成与安全陷阱。
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
- 所属 MOC: Go 服务工程
- 前置概念: Go Context
- 并列概念: Go 请求验证, [[go-middleware|Go 中间件]]
Go 认证与 JWT
一句话定义
JWT(JSON Web Token)是一种无状态的认证凭证,由 Header、Payload、Signature 三部分组成,Go 通过 golang-jwt/jwt 库实现创建、解析和验证。
核心机制 / 工作原理
JWT 结构:
- Header:
{"alg":"HS256","typ":"JWT"}— 声明签名算法和令牌类型。 - Payload:
{"sub":"user-123","exp":1719312000,"role":"admin"}— 声明(Claims),包含用户信息和过期时间。 - Signature:对 Header.Payload 的签名,防止篡改。HS256(对称)或 RS256(非对称)。
三部分用 . 连接,Base64URL 编码:xxxxx.yyyyy.zzzzz。
创建 JWT:
import "github.com/golang-jwt/jwt/v5"
// 定义 Claims
claims := jwt.MapClaims{
"sub": user.ID,
"role": user.Role,
"exp": time.Now().Add(15 * time.Minute).Unix(),
"iat": time.Now().Unix(),
}
// 使用 HS256 签名
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
解析与验证:
token, err := jwt.Parse(tokenString, 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(os.Getenv("JWT_SECRET")), nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
userID := claims["sub"].(string)
// 认证通过
}
中间件集成:
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
http.Error(w, "missing token", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
token, err := jwt.Parse(tokenString, keyFunc)
if err != nil || !token.Valid {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}
claims := token.Claims.(jwt.MapClaims)
ctx := context.WithValue(r.Context(), "user_id", claims["sub"])
next.ServeHTTP(w, r.WithContext(ctx))
})
}
刷新令牌机制:Access Token 短期(15min),Refresh Token 长期(7d)。Access Token 过期后,客户端用 Refresh Token 换取新的 Access Token,避免用户频繁登录。
最小例子 / 最小场景
// 登录接口:验证凭据 -> 签发 JWT
func loginHandler(w http.ResponseWriter, r *http.Request) {
user := authenticate(r) // 验证用户名密码
if user == nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": user.ID,
"exp": time.Now().Add(15 * time.Minute).Unix(),
})
tokenStr, _ := token.SignedString(jwtSecret)
json.NewEncoder(w).Encode(map[string]string{"token": tokenStr})
}
为什么重要
- 无状态认证:服务端无需存储 Session,天然适合分布式和微服务架构。
- 跨服务传递:JWT 可在服务间传递用户身份,网关验证后下游服务直接信任 Claims。
- 标准化:JWT 是 RFC 7519 标准,各语言和框架通用。
- 细粒度授权:Claims 中可携带角色、权限等信息,实现 RBAC。
边界与易混淆点
- JWT 不加密:Payload 是 Base64 编码,不是加密。任何人拿到 Token 都能解码读取内容。敏感信息不要放 Payload。
- 不能主动失效:JWT 无状态意味着服务端无法主动撤销已签发的 Token。解决方案:短有效期 + Refresh Token,或维护 Token 黑名单(牺牲无状态优势)。
- 算法混淆攻击:必须在
keyFunc中校验t.Method,否则攻击者可将算法改为none绕过签名验证。 - Session vs Token:传统 Web 应用用 Cookie + Session(服务端状态);SPA/移动端用 JWT(客户端状态)。两者不互斥,可混合使用。
- 存储位置:浏览器端 JWT 应存在
httpOnlyCookie 中而非 localStorage,防止 XSS 窃取。 - Refresh Token 轮转:每次使用 Refresh Token 时应签发新的 Refresh Token 并废弃旧的(Token Rotation),防止泄露后的长期滥用。