最稳妥方案是用 gorilla/sessions 管理登录态,它默认启用 HttpOnly、Secure 和 SameSite,支持多种后端存储,且避免手动操作 cookie 的安全风险。

用 gorilla/sessions 管理登录态最稳妥
Go 标准库没有开箱即用的会话管理方案,直接操作 http.SetCookie 容易漏掉 HttpOnly、Secure、SameSite 等关键属性,还容易被 CSRF 或会话固定攻击利用。推荐用 gorilla/sessions,它默认启用 HttpOnly 和 Secure(生产环境需配 HTTPS),并支持多种后端存储(内存、Redis、PostgreSQL)。
实操建议:
- 使用基于加密密钥的
cookiestore时,密钥长度必须 ≥ 32 字节,否则启动报错:crypto/aes: invalid key size 16 - 不要把用户密码、token 原样存进 session;只存
user_id,后续请求通过 ID 查数据库获取权限信息 - 登录成功后调用
session.Save(r, w),否则 cookie 不会写入响应头 - 退出时用
session.Options.MaxAge = -1并调用Save,强制客户端删除 cookie
store := cookie.NewCookieStore([]byte("32-byte-secret-key-must-be-long-enough"))
store.Options = &sessions.Options{
HttpOnly: true,
Secure: true, // 生产环境必须为 true(HTTPS 下)
SameSite: http.SameSiteStrictMode,
}JWT 认证适合无状态 API,但别手写签名逻辑
如果做前后端分离的 REST API,JWT 是更自然的选择;但 Go 生态里别自己拼字符串 + base64 + HMAC,容易出错。用 golang-jwt/jwt/v5(原 dgrijalva/jwt-go 的维护分支)更安全。
常见错误现象:
立即学习“go语言免费学习笔记(深入)”;
- 用
SigningMethodHS256但密钥硬编码在代码里,且未轮换 —— 一旦泄露,所有 token 失效风险高 - 校验时没检查
exp和nbf字段,导致过期 token 仍被接受 - 把 JWT 放在 URL 参数或 body 里传,而不是
Authorization: Bearer头中
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": 123,
"exp": time.Now().Add(24 * time.Hour).Unix(),
})
signedToken, _ := token.SignedString([]byte("your-32-byte-secret"))中间件里统一校验登录态,避免每个 handler 重复写
认证逻辑不该散落在各个 HTTP handler 里。用中间件封装校验流程,既减少重复,也便于统一处理未授权响应(如返回 401 或重定向到登录页)。
关键点:
- 中间件函数签名必须是
func(http.Handler) http.Handler形式,才能链式调用http.Handle - 校验失败时,不要用
return跳出中间件,而要用http.Error或显式w.WriteHeader + w.Write终止响应 - 若用 session,记得在中间件里调用
store.Get(r, "session-name")获取 session;若用 JWT,从r.Header.Get("Authorization")提取 token 并解析
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "auth-session")
if _, ok := session.Values["user_id"]; !ok {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
next.ServeHTTP(w, r)
})
}密码哈希必须用 bcrypt,且成本因子别设成 1
Go 标准库不提供密码哈希,golang.org/x/crypto/bcrypt 是事实标准。它自动加盐、抗彩虹表,且计算慢(可调),能有效拖慢暴力破解。
容易踩的坑:
- 用
bcrypt.GenerateFromPassword([]byte(pwd), 1)—— 成本因子太低(默认是 10),1 在现代 CPU 上几乎瞬间完成,失去防护意义 - 校验时用
bcrypt.CompareHashAndPassword,但没检查返回的 error 是否为bcrypt.ErrMismatchedHashAndPassword,而是笼统判断err != nil,可能掩盖其他错误(如解码失败) - 把明文密码记录到日志里(哪怕只是调试用)—— 实际部署必须禁用
hashed, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
err := bcrypt.CompareHashAndPassword(hashed, []byte("password123"))复杂点在于 session 存储选型和 JWT 密钥生命周期管理——前者影响横向扩展能力,后者决定整个认证体系的安全水位。这两个地方一旦定下来,就别轻易改。










