go密码哈希必须用golang.org/x/crypto/bcrypt,cost建议12或14;比对须用CompareHashAndPassword防时序攻击;cookie仅存随机session ID,设httpOnly、Secure、SameSite;session存储上线必用redis;登录需csrf防护,API场景可用JWT替代。

用 golang.org/x/crypto/bcrypt 安全哈希密码
明文存密码是硬性红线,Go 没有内置 bcrypt,必须用官方推荐的 golang.org/x/crypto/bcrypt。直接 bcrypt.GenerateFromPassword 生成哈希时,cost 参数别写死成 10——它影响 CPU 时间,现代服务器建议用 12 或 14;太低不安全,太高拖慢登录响应。
常见错误:把用户输入的原始密码和数据库里已哈希的值直接比对(==),结果永远失败。必须用 bcrypt.CompareHashAndPassword,它自带防时序攻击逻辑。
hash, err := bcrypt.GenerateFromPassword([]byte("user123"), 12) if err != nil { log.Fatal(err) } // 存入数据库的 hash 是类似 "$2a$12$..." 的字符串 err = bcrypt.CompareHashAndPassword(hash, []byte("user123")) // nil 表示匹配成功
用 http.Cookie + http.SetCookie 管理会话标识
不要把用户 ID、角色等敏感信息塞进 Cookie 值里明文传;只放一个随机、不可预测的会话 ID(session ID),后端查表映射到真实用户数据。Cookie 必须设 HttpOnly(防 xss 读取)、Secure(仅 https 传输)、SameSite=Strict 或 Lax(防 CSRF)。
容易忽略的点:MaxAge 和 Expires 别同时设;优先用 MaxAge(秒数),它更可靠;Expires 在某些客户端时区错乱时会出问题。
立即学习“go语言免费学习笔记(深入)”;
http.SetCookie(w, &http.Cookie{ Name: "session_id", Value: "abc123xyz", // 应为 crypto/rand 生成的 32 字节 base64 MaxAge: 3600, // 1 小时 HttpOnly: true, Secure: true, // 仅限 HTTPS SameSite: http.SameSiteLaxMode, })
用内存 map 或 Redis 存储 session 数据(避免全局变量)
开发阶段可用 sync.Map 模拟 session 存储,但上线必须换 Redis 或其他持久化方案——否则进程重启 session 全丢,且多实例部署时无法共享会话。
关键设计点:
- session ID 作 key,value 是结构体(含
UserID int、ExpiresAt time.Time、IP String等) - 每次请求都检查
ExpiresAt,过期就清除并返回 401 - 登录成功后生成新 session ID,旧 ID 立即失效(防会话固定攻击)
别用普通 map,并发读写 panic;也别在 handler 里直接操作全局 map——加锁复杂且易漏,sync.Map 是底线选择。
登录接口要校验 CSRF Token(尤其表单提交场景)
如果登录走 html 表单(POST /login),没 CSRF Token 就等于裸奔。前端需在 hidden input 里带 token,后端用 gorilla/csrf 或自己维护 token store 校验。
常见疏漏:
- 登录成功后没刷新 CSRF Token(导致后续操作 token 失效)
- 把 token 存在 Cookie 里又没设
SameSite=Strict,反而扩大攻击面 - 错误地对 API 登录(如 jsON POST)也强加 HTML 表单级 CSRF 防护,增加移动端调试成本
纯 API 场景可依赖 Authorization: Bearer + 短期 JWT,但 JWT 自带签名验证,别手写解析逻辑。
会话过期时间、密码哈希强度、CSRF 防护粒度,这三项一旦设错,修复成本远高于初期多花十分钟想清楚。