Go 认证与 JWT

Go 服务中基于 JWT 的认证实现,涵盖 Token 结构、创建解析、刷新机制、中间件集成与安全陷阱。

#type / concept #status / growing #tech / dev #resource / go

[!info] related notes

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 应存在 httpOnly Cookie 中而非 localStorage,防止 XSS 窃取。
  • Refresh Token 轮转:每次使用 Refresh Token 时应签发新的 Refresh Token 并废弃旧的(Token Rotation),防止泄露后的长期滥用。
创建于 2026/6/25 更新于 2026/6/25