Go语言JWT登录验证详解
时间:2026-01-22 18:06:58 143浏览 收藏
小伙伴们对Golang编程感兴趣吗?是否正在学习相关知识点?如果是,那么本文《Go语言JWT登录验证教程》,就很适合你,本篇文章讲解的知识点主要包括。在之后的文章中也会多多分享相关知识点,希望对大家的知识积累有所帮助!
应使用 github.com/golang-jwt/jwt/v5 替代旧版 jwt-go,因其修复了 alg=none 的严重安全漏洞,移除了 SigningMethodNone,并强制限定合法算法;需嵌入 jwt.RegisteredClaims、用 jwt.WithValidMethods 校验、密钥从环境变量加载、区分 access/refresh token 时效与存储,中间件统一返回标准 HTTP 状态码。

为什么用 github.com/golang-jwt/jwt/v5 而不是旧版 jwt-go
旧版 jwt-go(v3 及之前)在 2023 年被发现存在严重安全缺陷:当 alg 字段为 none 且签名为空时,部分配置下会跳过验签直接返回 nil 错误,导致伪造 token 可绕过认证。v5 版本彻底移除了 SigningMethodNone,并强制要求显式指定合法算法列表。
实操建议:
- 必须使用
github.com/golang-jwt/jwt/v5,别用github.com/dgrijalva/jwt-go或github.com/golang-jwt/jwt(无 /v5 后缀的默认是 v4,已归档) - 初始化
jwt.Parse时,keyFunc必须返回具体算法(如jwt.SigningMethodHS256)和密钥,不能返回nil或泛型interface{} - 验证失败时,检查错误是否为
jwt.ErrTokenExpired、jwt.ErrTokenUnverifiable等具体类型,而非仅判空
ParseWithClaims 中如何安全提取用户 ID 和权限字段
JWT payload 是可读的,敏感字段(如密码、手机号)绝不能塞进 token;只放最小必要标识,比如 user_id 和 role。自定义 claims 需嵌入 jwt.RegisteredClaims,否则无法触发标准校验(如过期、签发时间)。
示例结构定义:
type CustomClaims struct {
UserID uint `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}解析时必须传入该结构指针,并用 jwt.WithValidMethods 限定算法:
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, 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
}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))常见错误:
- 把
CustomClaims定义成非指针传给ParseWithClaims→ 解析成功但字段为空 - 忘记嵌入
jwt.RegisteredClaims→ExpiresAt等字段不参与自动校验 - 密钥硬编码在代码里 → 应从环境变量或 secret manager 加载
登录接口生成 token 时怎么控制有效期和刷新逻辑
不要用固定 24 小时;生产环境应区分 access token(短时效,15–60 分钟)和 refresh token(长时效,7 天,且需存 DB 绑定设备/IP/指纹)。access token 过期后,前端用 refresh token 换新 access token,而不是重新登录。
生成 access token 示例:
claims := &CustomClaims{
UserID: user.ID,
Role: user.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: strconv.FormatUint(uint64(user.ID), 10),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))关键点:
ExpiresAt必须用jwt.NewNumericDate包装time.Time,直接赋值 int64 会导致解析失败- 不要在 token 里存密码哈希、session ID 等可逆信息
- refresh token 必须服务端存储(Redis + 随机 UUID),验证时比对是否匹配且未被撤回
中间件里怎么统一拦截未授权请求并返回标准错误
Go HTTP 中间件应直接操作 http.ResponseWriter 并提前 return,避免后续 handler 执行。JWT 验证失败时,状态码必须是 401 Unauthorized(未登录)或 403 Forbidden(权限不足),不能混用 400 或 500。
典型中间件写法:
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "missing Authorization header", http.StatusUnauthorized)
return
}
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "invalid Authorization format", http.StatusUnauthorized)
return
}
tokenString := parts[1]
claims := &CustomClaims{}
_, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil {
switch {
case errors.Is(err, jwt.ErrTokenExpired):
http.Error(w, "token expired", http.StatusUnauthorized)
case errors.Is(err, jwt.ErrTokenInvalidClaim):
http.Error(w, "invalid claim", http.StatusUnauthorized)
default:
http.Error(w, "invalid token", http.StatusUnauthorized)
}
return
}
ctx := context.WithValue(r.Context(), "user_id", claims.UserID)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}容易忽略的坑:
- 没检查
authHeader是否为空就strings.Split→ panic - 验证通过后没把用户信息注入
context,下游 handler 只能重复解析 token - 错误响应没设
Content-Type: application/json,前端难解析
refresh token 的吊销和轮换逻辑比 access token 复杂得多,尤其是分布式部署时,务必用 Redis 的原子操作(SET key val EX 604800 NX)保证唯一性和时效性。别依赖内存 map 或本地文件。
今天带大家了解了的相关知识,希望对你有所帮助;关于Golang的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
-
505 收藏
-
503 收藏
-
502 收藏
-
502 收藏
-
502 收藏
-
485 收藏
-
242 收藏
-
282 收藏
-
297 收藏
-
107 收藏
-
281 收藏
-
462 收藏
-
156 收藏
-
167 收藏
-
440 收藏
-
144 收藏
-
134 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习