session 和 jwt 基础

本文的session 和jwt 在 authN 范畴下,只讨论前后端为同一公司或同一项目的架构。暂不深入涉及三方授权资源的认证架构。
  session-引用型 jwt-自包含
凭证内容 无意义随机串 签名过的 claims(userId、角色、exp)
验证方式 查存储(Redis/DB) 本地验签名,无数据库代价
吊销 删记录,立刻生效 做不到(签名改不了,只能等过期)
权限变更生效 改记录即生效 旧票里角色还是旧的
代价 每次请求碰存储 不可吊销 + claims 谁都能看

session是服务端保存信息,也就需要数据库,客户端持有一个随机字符串sessionId即可。

jwt 是无状态的(指不需要数据库),信息直接在token里。 token分为三个部分,1. 头部(header)元数据 2. 负载(payload) 3. 签名(signature);
常见使用HS256签名,头和payload是Base64编码的json字符串,签名是HMAC-SHA256算法计算。 也就意味着是个人就能看到你传的信息,但是无法篡改,篡改后签名对不上。
当然HS256是对称签名,为了安全可以提升至非对称签名RS256。

优点和缺点

session存在的问题就是要和数据库打交道,要处理的也是老问题,redis多实例,网关多实例等问题,但是网关统一处理还是很方便的。

jwt自持有信息,也就是说不用维护数据库,但jwt的问题在于成也萧何败也萧何,签名无法篡改,只能验证。也就意味着你发了30分钟的token,这个token就可以一直用到时间结束。
如果你想拦截它,那就必须引入数据库维护一个黑名单,然后每次要验证下token是否在黑名单中,这给正常业务突然加了个请求数据库的任务,这显然不合理,且相对于session的优势就有点小了。

实用架构

1.网关层 Session,网关 → 微服务内部传递 JWT

我在自己项目tryme里采用了这个模式。

架构流程

  1. 客户端(浏览器) ↔ Gateway 网关:使用 Authorization: Bearer sessionId
    • 登录、退出等用户信息在网关处理。Redis(集中存储 + 双向索引);
    • 用户退出,网关直接删除 Redis 的 Session,立刻失效。
  2. 网关校验 Session 通过之后,网关生成 JWT,转发给后端各个微服务。
  3. 各个微服务只校验 JWT 签名
    • 微服务不需要访问 Redis、不需要访问数据库,解析 JWT 拿到 userId、角色等。
  4. JWT 只在内网微服务之间流转,不会下发给浏览器客户端。

关键特点

  1. 对外是 Session,拥有 Session 全部优势:可随时注销、敏感信息存在网关 Redis,浏览器看不到。
  2. 内网用 JWT,微服务无状态,不用每个服务去查会话存储,网关做统一身份处理。 但是同时网关是鉴权单点:压力和故障面都集中在这一层。
  3. 内网 JWT 过期时间设置很短,比如 2‑5 分钟;就算泄露,影响窗口很小。
  4. 网关是唯一单点处理登录登出;微服务完全不碰登录逻辑。
  5. 多网关实例依然需要共享 Session Redis。

实战拓展

tryme项目:理论上网关和登录服务应该做个解耦,至少我在supcon实习时是这样的,在网关日志里可以看到登录服务是微服务调用,但是学习项目我不打算再弄个登录服务,于是把登入登出做在了网关,将修改密码删除用户等看作是业务,还是放在了后端。这就需要网关端存 Redis 的 sessionId和userId的双向索引。后端修改密码利用userId删除sessionId,达到及时踢出的效果


2. 客户端双 Token (JWT) + 网关 Session 黑名单兜底

架构设计

双token,一个短时效的token,一个长效的token。

长效token存在数据库,正常使用时只使用短效,有效则放行, 短效失效则用长效token续约。需要下线就删除长效token,但此时短效token还在。还是可以用一会,不过一般就15分钟,时间到了续约会发现无长效而续约失败。

如果不愿意放弃jwt架构,但又想有立即登出的效果,还是得引入session的机制。

业务请求过来网关:先校验 JWT 签名,同时判断 jwtId 是否在黑名单,其实就是session的机制了,黑名单其实就是redis维护,但从业务上一般认为这个黑名单比较短,所以主要开销还是在和redis通讯的网络开销。

也就是说jwt注定无法像session那样立刻登出,如果非要实现, 那jwt会退化成有状态的,意义就不大了,不如直接用session。jwt适合在对于登入登出不敏感的业务中使用。

工程细节

3. 同一个系统两套鉴权并存

很多后台管理系统就是这样:网页登录用 session;对外开放 API 给第三方调用,使用 JWT token。

拓展 OAuth2.0

上面的范畴都是后端服务和前端服务出自一家的,但是还存在广泛的第三方验证,有些第三方信息需要用户授权。比如打个游戏用qq登录,需要跳转到qq授权,让游戏获取你的头像等信息。

授权码模式 Authorization Code(网页、APP 第三方登录)

用户在第三方平台跳转到资源平台上授权,资源平台颁发一个code,该code需要配合第三方平台后端和资源平台约定好的密钥一起使用去获取资源凭证,该资源凭证决定第三方平台能在资源平台获取哪些资源.

例子:网页点击【使用微信登录】
完整流程:

  1. 用户在第三方网站 (客户端) 点「微信登录」,浏览器跳转到微信授权服务器页面。
  2. 微信这边:用户输入账号密码登录(这里微信内部用 Session),页面弹窗问用户:是否允许小游戏获取你的昵称头像?同意 / 拒绝。
  3. 用户点同意,微信重定向跳转回第三方网站,url 带上一个短暂的 authorization_code(授权码,一次性,很短命)。
  4. 第三方后端拿着这个 code,加上自己 client_id/client_secret,去后台请求微信授权服务器,换取 access_token。
  5. 微信校验,返回 access_token, 格式由提供方决定,微信是不透明的,Google 是 JWT 格式。
  6. 第三方后端拿着 access_token 请求微信资源服务器,拉取用户头像、昵称。
  7. 第三方拿到用户信息,在自己系统创建 / 登录账号。