JWT认证需用jwt-go签发校验token,设置exp/iat/nbf,仅存非敏感字段,密钥从环境加载,Set-Cookie防XSS;v4+需手动校验exp;中间件统一处理并注入用户上下文;密码用bcrypt安全存储;CSRF防护依场景而定,高危操作需二次验证;重置token单次有效;严格区分401/403/429等状态码。

用 jwt-go 实现基于 Token 的登录认证
Go 里最常用的身份认证方式是 JWT,核心就是签发和校验 token。别直接手写签名逻辑,用社区维护稳定的 jwt-go(v4+ 版本注意 API 变更)。签发时必须设置 exp 字段,且建议搭配 iat 和 nbf 做基础时间控制。
常见错误:用 HS256 但密钥硬编码在代码里;或者把用户密码明文塞进 token payload。正确做法是只放 user_id、role 这类非敏感字段,密码永远不进 token。
- 密钥必须从环境变量或配置中心加载,例如:
os.Getenv("JWT_SECRET") - 签发 token 后,响应头里设
Set-Cookie(带HttpOnly、Secure)比放 body 更防 XSS - v4+ 的
jwt.Parse默认不校验exp,得手动调用token.Valid或传入jwt.WithValidMethods
中间件里统一校验 token 并注入用户上下文
别在每个 handler 里重复解析 token,用 Gin 或 Chi 的中间件统一拦截 /api/** 路由。关键点在于:校验失败必须提前 return,且不能泄露具体失败原因(比如不区分 “token 过期” 和 “签名无效”,统一返回 401)。
校验通过后,把用户 ID 和角色塞进 context.Context,后续 handler 用 c.MustGet("user_id").(int64) 拿,而不是再查数据库。
立即学习“go语言免费学习笔记(深入)”;
- 中间件里用
ctx.Value()存用户数据,但注意它不是类型安全的,建议封装成带类型断言的 helper 函数 - 如果用 Gin,避免直接改
c.Keys,它本质是 map[string]interface{},容易拼错 key 名 - 对 /login、/refresh 这类免认证接口,中间件要显式跳过,别靠路径前缀硬匹配,用路由分组更可靠
用 bcrypt 安全存储密码,禁止明文或简单哈希
Go 标准库没有 bcrypt,必须用 golang.org/x/crypto/bcrypt。重点不是“怎么哈希”,而是“怎么用对”。常见坑是:用固定 cost=10 硬编码,或校验时没处理 bcrypt.ErrMismatchedHashAndPassword。
- 哈希时用
bcrypt.GenerateFromPassword(pwd, bcrypt.DefaultCost),生产环境可提到 12–14,但别盲目拉高影响登录延迟 - 校验必须用
bcrypt.CompareHashAndPassword(hash, pwd),别自己用 == 比较哈希值字符串 - 密码字段入库前,确保已 trim 空格,否则用户输空格也能登录成功
CSRF 防护与敏感操作二次验证的实际取舍
纯 REST API(前后端分离)通常不用 CSRF token,因为浏览器不会自动携带 Cookie 到跨域请求——前提是前端用 fetch 时没设 credentials: "include"。但如果用了,就得配 SameSite=Lax Cookie 和反 CSRF 头。
真正该加二次验证的是高危操作:删账号、改邮箱、提币。这时别依赖 session,用一次性 token + 短期 TTL(比如 5 分钟)+ 绑定操作类型和用户 ID。
- 不要用短信验证码做所有场景的二次验证,成本高、体验差;优先用 TOTP(如 Google Authenticator)或 WebAuthn
- 重置密码流程中,重置链接的 token 必须单次有效,且用完立刻失效,不能只靠过期时间
- 所有敏感操作日志必须落盘,包含 IP、User-Agent、时间戳、操作结果,哪怕只是 debug 级别
最常被忽略的是错误处理粒度:401 和 403 必须严格区分,前者是未认证,后者是已认证但无权限;而 429(限流)和 400(参数错)也不能混成一个“请求失败”。这些状态码直接影响前端逻辑分支。










