三周手撸企业级认证系统(一): 开篇对比认证方案演进

108 阅读11分钟

为什么要写这个系列

做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篇文章全部搞定。

每篇发布后,我会在评论区回复大家的问题,收集反馈,必要时更新文章。

最后

如果你也想系统地学习认证这块,欢迎跟着这个系列一起学。

如果你已经是认证这块的老手,也欢迎在评论区分享你的经验,指出我理解得不对的地方。

几个问题想问大家:

  1. 你们项目里用的什么认证方案?

    • Session?JWT?还是其他?
    • 为什么这样选?
  2. 有遇到什么坑吗?

    • JWT的坑?
    • 单点登录的坑?
    • 第三方登录的坑?
  3. 你最想了解认证的哪个方面?

    • 原理?
    • 实战?
    • 性能优化?
    • 安全加固?

评论区聊聊,你们的反馈会影响我后续文章的侧重点。

如果感兴趣这个系列,建议关注我,后续更新会第一时间通知你。

下一篇:《Spring Security + JWT完整实战》,预计这周末发布。

我们评论区见。