为什么要写这个系列
做Java后端这么多年,登录认证这块该踩的坑基本都踩过了:
- Session分布式问题?解决过,用Redis做共享
- JWT的Token续期?实现过双Token机制
- 单点登录?对接过CAS,也用过若依的方案
- 第三方登录?集成过微信、QQ、支付宝
但这些都是项目里遇到什么问题,临时去解决,零零散散,没有系统地整理过。
这次我想做的是:把认证这块从头到尾系统地串起来,形成一个完整的知识体系:
- Session为什么演进到JWT?
- 分布式环境下认证怎么做?
- 单点登录的核心原理是什么?
- OAuth2服务端怎么实现?
- 权限管理怎么和认证结合?
把零散的经验,整理成系统的方法论。
这个系列会写什么
我计划写10篇左右,大概是这样的:
第1篇:认证方案演进(对比篇)
- Session的问题
- JWT的优势和坑
- OAuth2的使用场景
- 三种方案对比
第2篇:Spring Security + JWT完整实战
- 从零搭建Spring Boot项目
- 集成Spring Security
- JWT生成和校验
- Access Token + Refresh Token
- 前后端完整对接
第3篇:Spring Cloud Gateway统一鉴权
- 为什么要在网关鉴权
- Gateway全局Filter
- JWT校验
- 用户信息传递到下游服务
第4篇:实现简单SSO
- SSO原理
- 搭建认证中心
- 多个系统接入
- Token共享机制
第5篇:OAuth2第三方登录
- 手写OAuth2授权服务器(搞清楚原理)
- 集成Gitee真实登录
- JustAuth快速接入
- 如何切换到微信/QQ
特别说明: 限于环境,个人没有企业资质申请不了微信OAuth。但这反而是个机会 —— 我会手写一个完整的OAuth2授权服务器(模拟微信/QQ的授权服务器),把授权码、Token、用户信息这三个接口的实现逻辑全部讲透。这样你不仅会用OAuth2,还会理解OAuth2服务端是怎么工作的。
第6篇:RBAC权限管理
- 数据库表设计
- 动态权限加载
- 菜单权限
- 按钮权限
- 接口权限
第7篇:数据权限控制
- MyBatis-Plus拦截器
- 按部门过滤
- 按角色过滤
- 自动添加WHERE条件
第8篇:安全加固
- 验证码(图形+短信)
- 登录限流
- 防暴力破解
- 防重放攻击
第9篇:生产级优化
- Token缓存
- 权限缓存
- 登录日志
- 异地登录检测
- 多设备管理
第10篇:系统总结
- 完整架构回顾
- 性能测试
- 部署方案
- 常见问题
这个系列的特点
1. 代码驱动,每篇都能跑
不是讲理论,而是:
- 每篇都有完整的可运行代码
- 数据库脚本、前端代码、配置文件全部提供
- 提供Docker Compose一键启动
- GitHub上的代码保证能跑,跑不起来算我的问题
2. 渐进式演进
不是一上来就给你个复杂的完整方案,而是:
- 第2篇:先搞定JWT登录(单体应用)
- 第3篇:加入Gateway(微服务)
- 第4篇:升级到SSO(多系统)
- 第5篇:加入OAuth2(第三方登录)
- 后面继续加权限、安全、优化
每一步都是可用的,不是过渡代码!
graph LR
A[第2篇: JWT登录] --> B[第3篇: +Gateway]
B --> C[第4篇: +SSO]
C --> D[第5篇: +OAuth2]
D --> E[第6篇: +RBAC]
E --> F[第9篇: 生产级]
style A fill:#90EE90
style B fill:#87CEEB
style C fill:#DDA0DD
style F fill:#FFD700
3. 参考多个优秀开源项目
我不会闭门造车,会参考:
- Spring Security官方示例(理解正统用法)
- 若依(RuoYi)(看业务实现)
- Pig(看微服务方案)
- eladmin(看代码优雅度)
然后提取它们的优点,结合自己的理解,写出更好的实现。
4. 不只是Client,还讲Server
这个系列的一个特色:不只教你怎么用,还教你怎么实现。
比如OAuth2:
- 大部分教程只讲怎么接入微信、QQ(Client视角)
- 但不讲OAuth2服务端怎么实现(Server视角)
我会:
- 手写一个完整的OAuth2授权服务器(本地运行)
- 实现授权接口、Token接口、用户信息接口
- 讲清楚每个接口为什么要这样设计
- 客户端对接本地服务器,完整跑通流程
然后再接入Gitee真实登录,对比本地服务器和Gitee的实现。
这样你就理解了微信、QQ的OAuth2服务器是怎么工作的。
graph TB
A[手写OAuth2服务器] --> B[理解服务端实现]
C[接入Gitee] --> D[理解客户端对接]
B --> E[完整理解OAuth2]
D --> E
style E fill:#FFD700
为什么要这样做?
因为只会用SDK的开发者太多了,真正理解原理的很少。当你手写过OAuth2服务器,你就能:
- 理解为什么要有授权码这个中间步骤
- 理解access_token和refresh_token的区别
- 理解scope权限范围的设计
- 遇到问题时知道是哪个环节出了问题
不只是会调API,而是理解为什么要这样设计。
5. 每问必答
如果你在跟着做的过程中遇到问题:
- 代码跑不起来
- 某个地方理解不了
- 想知道更多细节
在评论区留言,我一定回复。
先说说我现在的认知
以前做项目,登录认证这块基本是这样的:
- 小项目?Session走起,简单
- 中型项目?JWT,听说性能好
- 大型项目?复制若依或者Pig的认证模块
但我从来没系统地思考过:
- Session在分布式环境下为什么会有问题?
- JWT真的比Session好吗?有什么坑?
- 单点登录的Token怎么在多个系统间同步?
- OAuth2的授权码模式,每一步为什么要这样设计?
- 权限管理和认证怎么结合?
这次我想把这些问题搞透。
Session:传统方案的痛点
Session大家都用过,登录后服务器生成一个SessionID,存在Cookie里,下次请求带上这个ID就行。
// 传统的Session登录
@PostMapping("/login")
public Result login(String username, String password, HttpSession session) {
User user = userService.login(username, password);
if (user != null) {
session.setAttribute("userId", user.getId());
return Result.success("登录成功");
}
return Result.error("用户名或密码错误");
}
这方案简单,Spring Boot默认支持,基本不用配置。
但问题来了:
分布式环境下的Session共享
我之前一个项目,单机部署时好好的,后来加了两台服务器做负载均衡,结果用户登录后,第一次请求到服务器A,Session在A上,第二次请求到服务器B,B没有Session,用户就变成未登录了。
sequenceDiagram
participant U as 用户
participant LB as 负载均衡
participant A as 服务器A
participant B as 服务器B
participant R as Redis
U->>LB: 登录请求
LB->>A: 转发到A
A->>A: Session存在A的内存
A-->>U: 登录成功
U->>LB: 第二次请求
LB->>B: 转发到B
B->>B: B的内存里没有Session
B-->>U: 401 未登录
Note over U,B: 用户:我刚登录的,<br/>怎么又要登录?
后来我们用Redis做Session共享,每次请求都去Redis查Session。虽然Redis很快,但高并发下,这个查询开销也不能忽视。而且又引入了Redis依赖,系统复杂度上升。
移动端支持不友好
Session依赖Cookie,但很多移动端的HTTP客户端对Cookie支持不好。我们一个Android同事,为了让App自动管理Cookie,折腾了好几天。
CSRF攻击风险
基于Cookie的Session,天然容易受CSRF攻击。虽然可以加CSRF Token,但又增加了复杂度。
所以Session适合传统单体应用,不适合微服务、移动端。
JWT:无状态但有坑
JWT(JSON Web Token)是把用户信息编码成一个字符串,签名后发给客户端。下次请求带上这个Token,服务器验证签名,解析出用户信息。
// JWT生成
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getId().toString())
.claim("username", user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
JWT的优势
最大的优势是无状态。服务器不用存任何东西,所有信息都在Token里。分布式部署时,哪台服务器收到请求,都能自己验证Token。
sequenceDiagram
participant U as 用户
participant A as 服务器A
participant B as 服务器B
U->>A: 登录
A->>A: 生成JWT(包含用户信息)
A-->>U: 返回JWT
U->>B: 请求(带JWT)
B->>B: 验证JWT签名
B->>B: 解析出用户信息
B-->>U: 响应
Note over B: 不需要查Redis,<br/>不需要查数据库,<br/>直接验证就行
而且JWT不依赖Cookie,移动端用起来很方便。
但JWT有几个坑
我们用JWT时踩过这些坑:
Token无法主动失效
Session可以在服务器删除,用户立即失效。但JWT一旦发出去,在过期前服务器无法让它失效。
比如用户改了密码,旧Token应该立即失效,但做不到。后来我们搞了Token黑名单(用Redis存),但这又引入了状态,违背了JWT无状态的初衷。
Token续期麻烦
Session可以自动续期,只要用户一直操作,Session就不过期。但JWT过期了就是过期了。
后来我们搞了双Token:Access Token(2小时)和Refresh Token(7天)。Access Token过期后,用Refresh Token换新的。但这又增加了复杂度。
sequenceDiagram
participant C as 客户端
participant S as 服务器
C->>S: 登录
S-->>C: Access Token(2h) + Refresh Token(7d)
Note over C: 2小时后
C->>S: 请求(带过期的Access Token)
S-->>C: 401 Token过期
C->>S: 刷新Token(带Refresh Token)
S-->>C: 新的Access Token(2h)
C->>S: 请求(带新Token)
S-->>C: 成功
Token体积大
Session只存一个ID,很小。JWT要把用户信息编码进去,Token会比较大。每次请求都带这么大一个Token,也是开销。
所以JWT适合微服务、前后端分离,但要注意它的坑。
OAuth2:第三方登录的标准
OAuth2和前面两个不一样,它不是用来做自己系统登录的,是用来做第三方登录的。
你在某个网站点"微信登录"、"GitHub登录",这就是OAuth2。
OAuth2的核心流程
sequenceDiagram
participant U as 用户
participant C as 我们的网站
participant G as Gitee授权服务器
participant R as Gitee资源服务器
U->>C: 点击"Gitee登录"
C-->>U: 跳转到Gitee授权页
U->>G: 输入账号密码,同意授权
G-->>U: 跳转回我们网站(带code)
U->>C: 访问回调地址(带code)
C->>G: 用code换access_token
G-->>C: 返回access_token
C->>R: 用access_token获取用户信息
R-->>C: 返回用户信息
C->>C: 创建本地用户,生成JWT
C-->>U: 登录成功
关键点:
- 用户密码在Gitee输入,我们拿不到
- Gitee给我们access_token,不是密码
- 我们用access_token只能拿到授权的信息
为什么要用OAuth2
最大的好处是降低注册门槛。很多用户懒得注册,但都有微信、GitHub账号,一键登录体验好。
而且我们省事,不用管密码安全、密码找回这些麻烦事。
个人开发者能玩吗
微信登录需要企业资质,个人搞不了。但Gitee、GitHub个人能申请,而且免费。
更重要的是:
我会手写一个完整的OAuth2授权服务器(本地运行),模拟微信/QQ的授权流程。这样你能同时学到:
- **客户端视角:**怎么对接OAuth2(用Gitee真实演示)
- **服务端视角:**OAuth2授权服务器怎么实现
graph LR
A[你的应用] --> B[本地OAuth2服务器<br/>模拟微信]
A --> C[Gitee真实OAuth2]
style B fill:#FFE4B5
style C fill:#90EE90
B -.->|理解原理| D[搞懂服务端实现]
C -.->|生产可用| E[真实接入]
通过本地服务器,你能理解:
- 授权码怎么生成和校验
- access_token怎么管理
- 用户信息接口怎么实现
- 为什么要这样设计
OAuth2的流程是标准的,学会了本地服务器和Gitee,换成微信只是改配置。代码逻辑完全一样。
关于参考项目
我不会闭门造车,会参考这些优秀的开源项目:
Spring Security官方示例
学习正统用法,理解框架设计
若依(RuoYi)
看业务实现,特别是:
- 权限管理的表设计
- 登录日志、操作日志
- 数据权限的实现
Pig
微服务方案参考:
- Gateway鉴权
- 多租户实现
- 微服务间用户信息传递
eladmin
代码优雅度参考:
- 代码结构
- 异常处理
- 权限注解的使用
我会用根据我的经验分析这些项目,提取它们的优点,结合自己的理解,写出更好的实现。
系列的几个承诺
1. 代码保证能跑
每一篇的代码我都会自己跑通、测试过,然后才发文章。
如果你按照我的步骤来,跑不起来,那是我的问题。在评论区告诉我,我一定修复。
2. 提供完整环境
每一篇都提供:
- SQL脚本(包含测试数据)
- 配置文件
- 前端代码
- Docker Compose(一键启动所有依赖)
你只需要:
git clone xxx
cd v2-jwt-auth
docker-compose up -d
3. 每问必答
如果你在跟着做的过程中:
- 代码跑不起来
- 某个地方理解不了
- 想知道更深入的原理
- 想看某个功能的实现
在评论区留言,我一定回复。
不是那种敷衍的回复,而是认真解答,必要时补充代码示例。
4. 持续更新
这个系列我会持续更新,三周内必然全部更新完。
平均每两天一篇,三周10篇文章全部搞定。
每篇发布后,我会在评论区回复大家的问题,收集反馈,必要时更新文章。
最后
如果你也想系统地学习认证这块,欢迎跟着这个系列一起学。
如果你已经是认证这块的老手,也欢迎在评论区分享你的经验,指出我理解得不对的地方。
几个问题想问大家:
-
你们项目里用的什么认证方案?
- Session?JWT?还是其他?
- 为什么这样选?
-
有遇到什么坑吗?
- JWT的坑?
- 单点登录的坑?
- 第三方登录的坑?
-
你最想了解认证的哪个方面?
- 原理?
- 实战?
- 性能优化?
- 安全加固?
评论区聊聊,你们的反馈会影响我后续文章的侧重点。
如果感兴趣这个系列,建议关注我,后续更新会第一时间通知你。
下一篇:《Spring Security + JWT完整实战》,预计这周末发布。
我们评论区见。