接口鉴权演进:从 Session 到 JWT 再到 OAuth2
接口鉴权演进:从 Session 到 JWT 再到 OAuth2
背景与动机
任何一个对外提供能力的后端系统,绕不开一个问题:这个请求到底是谁、有没有权限访问?早期系统用户少、服务单体化,这个问题很简单——登录后在服务端记一笔会话就够了。但随着业务演进,我们通常会遇到三类痛点:
- 水平扩容难:传统 Session 默认存内存,多实例部署后用户登录态在 A 机器、请求落到 B 机器就失效,要么做 Session 复制,要么上集中式存储。
- 前后端分离与多端:前端不再是服务端渲染,移动端、小程序、第三方合作伙伴都要调同一套接口,Cookie 在跨域和原生场景里很别扭。
- 第三方接入与授权:当业务需要让「别的公司/应用」在用户授权下访问我们的数据(比如「用 GitHub 登录」),简单的账号密码模型完全不够用。
于是鉴权方案沿着 单体 Session → 无状态 JWT → 标准授权框架 OAuth2 的路径演进。本文用一条「用户登录→访问受保护接口→开放给第三方」的故事线,讲清楚三种方案各自的适用边界和落地代码。
核心概念与技术选型
三种方案不是替代关系,而是不同场景下的取舍:
| 维度 | Session | JWT | OAuth2 |
|---|---|---|---|
| 状态位置 | 服务端(有状态) | 客户端(无状态) | 授权服务器颁发令牌(无状态) |
| 扩容友好 | 需集中存储(Redis) | 天然友好 | 天然友好 |
| 跨域/多端 | 依赖 Cookie | 任意 Header 携带 | 任意 Header 携带 |
| 第三方授权 | 不支持 | 不擅长 | 原生支持 |
| 吊销难度 | 易(删服务端记录) | 难(需黑名单/短过期) | 中(可吊销授权) |
选型建议:内部单体系统、强管控的后台优先 Session;需要无状态、跨端、自研多服务的 BFF/网关层优先 JWT;需要开放平台、第三方登录、委托授权时上 OAuth2。实际项目里三者常共存:内部用 JWT,对外合作走 OAuth2。
分步实现过程
1. 最朴素的 Session 方案(Spring Boot)
登录成功后,把用户信息塞进 HttpSession,后续请求靠拦截器校验。
// 登录接口:服务端在会话中保存登录态
@PostMapping("/login")
public String login(String username, String password, HttpSession session) {
User user = userService.verify(username, password);
if (user == null) throw new RuntimeException("账号或密码错误");
// 关键:服务端持有会话状态
session.setAttribute("uid", user.getId());
session.setAttribute("role", user.getRole());
return "登录成功";
}
// 拦截器校验:每次请求从服务端取会话
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
HttpSession session = req.getSession(false);
if (session == null || session.getAttribute("uid") == null) {
resp.setStatus(401);
return false;
}
return true;
}
为支持多实例,把 Session 存到 Redis:
# application.yml:用 Redis 集中存储 Session
spring:
session:
store-type: redis
redis:
host: 10.138.1.165
port: 6379
2. 无状态 JWT 方案
把登录态编码进令牌,服务端不保存,靠签名校验。用 java-jwt 生成与校验:
// 登录:签发 JWT(设置 2 小时过期 + 防重放 jti)
public String issueToken(User user) {
Date now = new Date();
return JWT.create()
.withSubject(user.getId().toString())
.withClaim("role", user.getRole())
.withIssuedAt(now)
.withExpiresAt(new Date(now.getTime() + 2 * 3600 * 1000))
.withJWTId(UUID.randomUUID().toString()) // jti,用于黑名单吊销
.sign(Algorithm.HMAC256("你的密钥"));
}
// 拦截器:解析并校验签名与过期,无需查库
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
String token = req.getHeader("Authorization").replace("Bearer ", "");
try {
DecodedJWT jwt = JWT.require(Algorithm.HMAC256("你的密钥")).build().verify(token);
req.setAttribute("uid", jwt.getSubject());
return true;
} catch (JWTVerificationException e) {
resp.setStatus(401);
return false;
}
}
3. OAuth2 第三方授权(以授权码模式为例)
当「旅游 App」想让用户用「微信」登录时,流程是:重定向到授权服务器 → 用户同意 → 拿 code → 换 access_token。用 Spring Security 一行配置即可对接:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.authorizationEndpoint(e -> e.baseUri("/oauth2/authorize"))
.redirectionEndpoint(e -> e.baseUri("/login/oauth2/code/*"))
)
.authorizeRequests(a -> a
.anyRequest().authenticated()
);
return http.build();
}
}
客户端配置(application.yml)登记第三方(以 GitHub 为例):
spring:
security:
oauth2:
client:
registration:
github:
client-id: 你的client_id
client-secret: 你的client_secret
scope: read:user
授权成功后,框架自动用 access_token 拉取用户信息并建立本地会话,业务代码无需关心第三方细节。
踩坑、边界条件与权衡取舍
- JWT 无法即时吊销:JWT 过期前服务端无法强制失效。对策:缩短过期时间(如 15 分钟)+ 刷新令牌(refresh token)续期;或对关键操作维护 Redis 黑名单(jti 校验)。黑名单会让「无状态」打折扣,需评估。
- 密钥与算法陷阱:JWT 常见攻击是用
alg: none绕过签名,或把密钥写死在代码里。务必用非对称算法(RS256)把公钥分发给药理端,私钥只在签发端保存;密钥定期轮换。 - Session 扩容并非只能上 Redis:若实例数少,可用粘性会话(IP 哈希)降低复杂度,但实例重启会掉登录,需权衡可用性。
- OAuth2 的 scope 粒度:授权范围太粗会过度授权,太细又增加交互成本。建议按「资源 + 操作」设计 scope(如
order:read、order:write)。 - Token 泄露风险:Bearer Token 一旦泄露等同于账号被盗。必须全程 HTTPS,前端避免把 token 存 localStorage(易遭 XSS),优先存内存或 HttpOnly Cookie。
- 不要把 JWT 当万能:JWT 适合放用户 ID、角色等非敏感声明,绝不放密码、身份证号等敏感数据——因为 JWT 只是签名未加密(除非用 JWE)。
小结与延伸阅读
鉴权的演进本质是「状态放置」与「信任边界」的权衡:Session 把状态放服务端、管控强但扩容重;JWT 把状态交给客户端、扩容轻但吊销难;OAuth2 把「授权」标准化,解决第三方协作。落地时不必二选一——内部服务用 JWT 做无状态网关鉴权,对外合作走 OAuth2,是最常见也最稳妥的组合。
延伸阅读:
- RFC 6749(OAuth 2.0 授权框架)与 RFC 7519(JWT)
- Spring Security 官方文档的 Servlet OAuth2 Client 章节
- 「The OAuth 2.0 Authorization Framework: Bearer Token Usage」了解令牌使用规范
- 关于 JWT 吊销的「黑名单 vs 短过期 + 刷新」两种工程实践对比
更多推荐




所有评论(0)