从原理到实战:JWT 认证、双 Token 刷新与 SSO 单点登录全解析
前端JWT
JWT 简介
JWT(JSON Web Token):把登录信息封装成 JSON,再用密钥签名后得到的字符串。一般来讲结构是
header.payload.signature(均为 Base64URL),常放在 Authorization: Bearer <token> 头或 URL 参数中优点
- 无状态:服务器只验证签名,不必存会话,易水平扩展
- 跨域 / 跨服务:一个令牌可在多微服务、前后端分离或移动端间复用
- 自包含:payload 可携带角色、过期时间等,减少额外查询
- 传输方便:纯文本,直接随请求发送
缺点
- 难撤销:除非设置短过期或维护黑名单,否则无法即时失效
- 泄露风险:客户端保存,一旦被窃取即可冒充用户直至过期
- 体积偏大:比简单 session ID 长,频繁请求会占带宽
- 默认不加密:payload 可被查看,敏感信息需另行加密或不放,其实是明文的,不能叫加密,只能叫签名
- 密钥管理复杂:密钥轮换、算法选择失误都可能带来安全隐患
基于 JWT 实现 Refresh Token
登录
颁发 Access Token(短效) + Refresh Token(长效)。Refresh Token 和过期时间保存到DB里面。Refresh Token 由后端通过 Set-Cookie 设置,前端无法读取。如果 Refresh Token 过期,会被浏览器自动清除。每次登录都会重新生成并设置新的 Refresh Token,它相当于一个有限时间内有效的用户身份标识。
请求资源
每次请求都附带 Access Token(通常保存在内存里,放在 Authorization 头中)。如果后端返回 401,说明 Access Token 已过期,前端应调用
/refresh 获取新的 Access Token。刷新成功后,前端应自动重试原始请求。用户登录过期
页面初始化时,前端调用
/refresh 接口试探是否登录过。浏览器会自动附带 Refresh Token(如果仍存在)。如果刷新成功,则恢复登录状态(获得新的 Access Token + 设置新的 Refresh Token)。如果刷新失败(Refresh Token 不存在或已过期),则判断为未登录或登录已过期,跳转登录页。Access Token 过期
用 Refresh Token 请求新的 Access Token。(
/refresh ),后端通过 Refresh Token 判断用户身份。Refresh Token 不需要手动携带,浏览器会自动附加到请求中(存于 Cookie)。如果 Cookie 过期,浏览器不会发送该 Cookie,刷新会失败。刷新成功
后端返回新的 Access Token 和新的 Refresh Token(用于下一次刷新)。但此时 Refresh Token 的过期时间不会被延长,仍然使用最初登录时设定的有效期。因此,Refresh Token 到期后仍需重新登录。
Refresh Token 过期 / 不匹配
说明身份已失效。刷新失败,前端应清除本地状态并跳转至登录页,强制用户重新登录。
基于 JWT 实现强制下线(Stateful)
当管理员执行强制下线操作时,后端会将该用户的
token 或 jti(JWT 的唯一标识)存入 Redis,并设置一个过期时间(等同于 Token 的剩余有效期)。 每次用户请求时,拦截器会先去 Redis 查询。如果在,直接返回 401,要求重新登录。如果不在,再进行正常的 JWT 校验。SSO 单点登录 Okta
SP-Initiated(服务提供商发起) 登录
对于 Okta 这样的单点登录系统,Okta 的 Cookie 只能种在 okta.com 域名下,而用户的后端无法直接给前端域名种 Cookie。为了解决这个问题,Okta 采用了 OIDC (OpenID Connect) 协议。当用户在 Okta 界面输入密码并验证成功后,发生了以下步骤:
- 浏览器重定向 (Redirect): Okta 不直接给后端发数据,而是让用户的浏览器跳转回预先设定的
Redirect URI(例如your-app.com/callback)。
- 携带授权码 (Code): 在跳转的 URL 后面,Okta 会拼接一个临时的随机字符串,叫做 Authorization Code。URL 示例:
https://your-app.com/callback?code=xxxxxx...
- 换取 Token: 后端拿到这个
code后,立刻从后台发一个请求给 Okta,说:“我拿到了这个码,请给我真正的用户信息。”
- 校验与发放: Okta 确认无误后,返回两个核心令牌:
- ID Token: 包含用户的姓名、邮箱等身份信息。
- Access Token: 证明用户已授权。
- 创建 Session/JWT: 后端根据 Okta 返回的用户信息,在自己的数据库里查到对应的本地用户 ID,然后生成该用户的 Session ID 或 JWT。
- 写入响应头 (Set-Cookie): 后端在给前端的
callback请求返回响应时,会在 HTTP Header 中加入:Set-Cookie: session_id=abc12345; Domain=your-app.com; HttpOnly; Secure
- 前端落地: 浏览器接收到这个响应,自动将 Cookie 存在
your-app.com域名下。