用户鉴权与会话:密码哈希、JWT vs Session、OAuth2 与刷新令牌

全面讲解微型博客的用户鉴权与会话管理:注册登录流程、密码哈希(bcrypt/argon2)与加盐、Session 与 JWT 两种会话方案对比、OAuth2/第三方登录(Google/GitHub)、刷新令牌与访问令牌、设备管理与登录风控,以及鉴权的安全加固清单。

鉴权是微型博客的「大门」——它回答「你是谁」并给你一把进门的钥匙。从最朴素的「用户名 + 密码」,到现代平台的「OAuth2 第三方登录 + 刷新令牌」,鉴权方案的每一次演进都在平衡安全、体验与可扩展性。

本文系统讲解:密码哈希、Session vs JWT、OAuth2 登录、访问/刷新令牌、设备管理。

一、注册与登录流程

1.1 标准流程

注册:用户名/邮箱 + 密码 → 校验唯一性 → 哈希存储 → 建用户
登录:输入凭据 → 校验密码哈希 → 发令牌 → 建立会话
会话:每次请求带令牌 → 校验 → 放行
退出:吊销令牌 / 清除会话

1.2 用户表

CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  username VARCHAR(30) UNIQUE NOT NULL,
  email VARCHAR(255) UNIQUE,
  password_hash VARCHAR(255),       -- 可为空(第三方登录用户)
  avatar_url TEXT,
  created_at TIMESTAMPTZ DEFAULT now(),
  last_login_at TIMESTAMPTZ,
  status SMALLINT DEFAULT 1
);

1.3 密码校验

// 注册时哈希
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
user.PasswordHash = string(hash)

// 登录时比对
err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
if err != nil { /* 密码错误 */ }

一句话总结:鉴权的第一步是把「明文密码」永远拒之门外——用带随机盐的慢哈希(bcrypt/argon2)存储,比对时同样走哈希函数。

二、密码哈希与安全存储

2.1 为什么不能存明文

数据库泄露 → 明文密码全部暴露 → 撞库(同一密码多平台复用)→ 连锁损失
慢哈希的目的:即使拿到哈希,也难以暴力破解(每次尝试都昂贵)

2.2 bcrypt vs argon2

算法特点适用
bcrypt内置盐、可调 cost最常用,库支持广
argon2内存硬(抗 GPU/ASIC)最高安全要求
scrypt内存硬次优选择
MD5/SHA1快(不适合密码)❌ 绝不用于密码
// argon2 示例(更安全)
salt := make([]byte, 16)
rand.Read(salt)
hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
// 存储格式:$argon2id$v=19$m=65536,t=1,p=4$<salt>$<hash>

2.3 密码策略

- 最小长度(≥8),不强求复杂字符(长度优先)
- 尝试次数限制(登录限流,防爆破)
- 泄露检测:检查常见弱密码/撞库名单
- 强制重置:可疑登录后

一句话总结:密码哈希的准则是「慢且带盐」——bcrypt/argon2 让每次猜测都昂贵,随机盐防彩虹表,长度与限流补足策略。

三、Session 与 JWT

3.1 Session(服务端会话)

登录 → 服务端生成 session_id → 存 Redis/内存 → 下发 cookie
请求 → cookie 携带 session_id → 服务端查 session → 校验 → 放行
优点缺点
可主动吊销服务端要存状态
可存任意数据分布式需共享 session 存储

3.2 JWT(无状态令牌)

登录 → 服务端签发 JWT(header.payload.signature)→ 下发
请求 → Authorization: Bearer <jwt> → 验签 → 放行(无需查库)
优点缺点
无状态,易水平扩展默认难主动吊销
可携带用户信息payload 过大膨胀

3.3 对比与选型

维度SessionJWT
状态存储服务端(Redis)无状态
吊销即时需黑名单/短过期
扩展性需共享存储天然水平扩展
安全cookie 需防 CSRFtoken 需防 XSS 窃取
微型博客选型单机/中小多实例/开放 API
实践建议:
  面向 API/第三方 → JWT
  面向 Web 页面 → Session(HttpOnly Cookie)
  混合:JWT + Redis 黑名单(吊销兜底)

一句话总结:Session 用服务端状态换「可吊销」,JWT 用无状态换「易扩展」——微型博客常见做法是 Session 管 Web、JWT 管 API。

四、OAuth2 与第三方登录

4.1 为什么需要

用户不想注册第三个账号 → 「用 Google 登录」「用 GitHub 登录」
OAuth2 授权码流程让平台把「身份验证」委托给第三方,同时获取受控权限

4.2 授权码流程

1. 用户点「用 Google 登录」→ 跳转 Google
2. Google 登录并同意 → 回调 code
3. 后端用 code + client_secret 换 access_token
4. 用 access_token 调 Google 用户信息接口
5. 匹配/创建本地用户 → 建立本地会话

4.3 第三方账号绑定

CREATE TABLE oauth_accounts (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL REFERENCES users(id),
  provider VARCHAR(20) NOT NULL,      -- google/github
  provider_uid VARCHAR(100) NOT NULL, -- 第三方唯一 id
  access_token VARCHAR(255),
  refresh_token VARCHAR(255),
  expires_at TIMESTAMPTZ,
  UNIQUE (provider, provider_uid)
);

4.4 安全要点

- state 参数防 CSRF(回调时校验)
- 只申请最小 scope(profile/email)
- provider_uid 是唯一键,防重复绑定
- 第三方 token 加密存储或仅内存使用
- 邮箱已验证才作为登录标识

一句话总结:OAuth2 第三方登录把「验证」外包给平台,但本地必须用 provider_uid 做唯一关联、用 state 防 CSRF、用最小 scope 控权限。

五、访问令牌与刷新令牌

5.1 为什么分两种

- 访问令牌(Access Token):短期(15min~2h),每次请求携带
- 刷新令牌(Refresh Token):长期(7~30天),仅用于换新访问令牌

分离的好处:
  - 访问令牌短命 → 泄露影响小
  - 刷新令牌长命 → 用户体验好(少登录)
  - 刷新令牌可吊销 → 找回被盗会话

5.2 刷新流程

访问令牌过期
  → 客户端用 refresh_token 调 /oauth/token
  → 服务端校验 refresh_token + 轮换(旧作废,发新的)
  → 返回新 access_token + 新 refresh_token

5.3 刷新令牌轮换

- 每次刷新都发新 refresh_token,旧的立即失效(防重放)
- 检测到旧 token 重复使用 → 视为被盗 → 吊销整条会话链
- refresh_token 存储:HttpOnly Cookie 或服务端

5.4 实现要点

type TokenPair struct {
    AccessToken  string `json:"access_token"`
    RefreshToken string `json:"refresh_token"`
    ExpiresIn    int64  `json:"expires_in"`
}
// access: 15min  JWT
// refresh: 14天  随机字符串存 Redis(可吊销)

一句话总结:双令牌把「频繁验证」与「长期登录」解耦——短命 access 降低泄露面,轮换的 refresh 让长期会话既方便又可吊销。

六、设备管理与风控

6.1 设备会话

CREATE TABLE sessions (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  device_id VARCHAR(64),        -- 设备指纹
  platform VARCHAR(20),         -- ios/android/web
  refresh_token_hash VARCHAR(128),
  ip VARCHAR(45),
  user_agent TEXT,
  created_at TIMESTAMPTZ,
  last_seen_at TIMESTAMPTZ,
  revoked BOOLEAN DEFAULT FALSE
);

6.2 登录风控

- 异常 IP / 异地登录 → 二次验证或告警
- 尝试次数限制 + 锁定(短时连续失败)
- 新设备登录 → 通知用户
- 高风险操作(改邮箱、改密码)→ 重新鉴权

6.3 会话管理

- 「退出所有设备」→ 批量吊销 sessions
- 「查看活跃会话」→ 按设备列表
- 改密码 → 吊销全部会话(安全实践)

一句话总结:会话不止「发出去」,还要「可管理」——设备维度的会话表支撑查看、吊销、风控,是鉴权的运营面。

七、安全加固清单

主题做法
密码bcrypt/argon2 + 随机盐,永不存明文
传输全站 HTTPS,禁止明文登录
Web 会话HttpOnly + SameSite Cookie 防 XSS/CSRF
APIBearer Token 放 Authorization 头
刷新令牌轮换 + 存储哈希 + 检测重放即吊销
风控限流、异地告警、新设备确认
日志登录/登出/失败审计日志

鉴权是微型博客的第一道防线:密码哈希守住账号,双令牌平衡安全与体验,OAuth2 开放第三方入口,会话管理提供运营面。把这几层做扎实,用户的账号就守住了。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「miniblog」更多文章

  1. 速率限制与防滥用:令牌桶、滑动窗口与分布式限流
  2. 通知系统:通知类型、聚合去重、多端同步与推送架构
  3. 评论与互动系统:评论树、@提及、点赞转发与互动计数一致性