从弃用到新生:全面解析 Spring Authorization Server、OAuth 2.1 与 JWT 的演进之路

在 Java 后端开发生态中,Spring Security 一真是安全领域的绝对霸主。然而,随着安全协议标准的演进(特别是从 OAuth 2.0 向 OAuth 2.1 的过渡),Spring 官方在 OAuth2 授权服务器领域的布局也经历了一场彻底的"推倒重来"。

很多开发者在面对 spring-security-oauthspring-security-oauth2 以及全新的 Spring Authorization Server 时,常常感到混淆。本文将为你理清这些概念的历史演进、密码模式被废弃的深层原因,以及 JWT 在现代 OAuth2/OIDC 架构中的核心应用。


一、 核心概念理清:三兄弟的身份定位

在开始前,我们必须先认清 Spring 历史上面对 OAuth2 的三个核心库,它们有着完全不同的生命周期和定位:

库名称 历史地位与现状 支持的模式与职责
spring-security-oauth 已废弃 (Deprecated/End-of-Life) 最后一版为 2.5.x。虽然在一些老旧系统中仍能运行,但官方已停止维护。 支持传统的授权码、客户端、密码等所有模式。包含客户端、资源服务器和授权服务器三合一。
spring-security-oauth2 并非独立依赖包 自 Spring Security 5.x 开始,其客户端(Client)和资源服务器(Resource Server)能力已被深度整合进 Spring Security 核心库中。在 Spring Boot 3 / Spring Security 6.x 中更加成熟。 负责消费/验证 令牌(Client / Resource Server),不包含颁发令牌的"授权服务器"功能。
Spring Authorization Server 官方唯一推荐的全新授权服务器 当前 1.5.x 处于活跃维护状态。未来新特性将直接并入 Spring Security 7.0+ 中。 专门用于颁发令牌的"授权服务器"(Authorization Server),原生支持 OAuth 2.1 与 OIDC 1.0 标准。

!NOTE 版本选型建议: 如果你正在启动新项目,或正在升级到 Spring Boot 3,请果断放弃旧的 spring-security-oauth。使用 Spring Security 6.x 内置的客户端/资源服务器模块,并搭配 Spring Authorization Server 1.5.x(或关注即将到来的 Spring Security 7.0 统一版本)来构建你的认证授权中心。


二、 时代终结:为什么密码模式(Password Grant)被无情抛弃?

在旧版 spring-security-oauth 中,**密码模式(Resource Owner Password Credentials Grant)**非常流行。很多开发者图省事,直接在前端(如 Vue/React 或 APP)让用户输入账号密码,然后通过 Post 请求直接发送给后端换取 Access Token。

然而,在 Spring Security 5.8 中,密码模式已被正式标记为废弃(Deprecated)并移除 。全新构建的 Spring Authorization Server 从诞生之日起,就完全不支持密码模式。

为什么废弃密码模式?

  1. 违背了 OAuth 的初衷:OAuth 协议的核心目的是**"去信任化委托"**------第三方应用在不获取用户密码的前提下,获得受限制的资源访问权限。而密码模式要求用户将最敏感的"账号密码"直接暴露给客户端应用,一旦客户端被攻破或存在恶意收集,用户凭证将彻底泄露。
  2. OAuth 2.1 规范的收紧:正在制定的 OAuth 2.1 规范已经将密码模式从标准中完全移除。为了紧跟国际安全标准,Spring 团队选择坚定地执行这一规范。

现代架构下的替代方案

  • 授权码模式(Authorization Code)+ PKCE
    • 适用场景:单页应用(SPA)、移动端 APP 以及传统的 Web 应用。
    • 原理:通过动态生成的 Code Verifier 和 Code Challenge(即 PKCE 机制),即使在没有 Client Secret 的不安全客户端上,也能安全地完成授权码交换,彻底杜绝了授权码拦截攻击。
  • 客户端模式(Client Credentials)
    • 适用场景:微服务架构中服务与服务之间的通信(M2M),不涉及具体的用户上下文。
  • 首方应用 session/Cookie 机制
    • 如果你的前后端属于同一个系统(First-party Application),Spring 官方更推荐使用传统的、受 CSRF 保护的 Cookie/Session 机制进行身份保持,而不是强行套用 OAuth2 流程。

三、 JWT:OAuth2 与 OIDC 生态的令牌增强利器

在 OAuth2 生态中,JWT (JSON Web Token) 可以被理解为一种高级的令牌增强技术。它将令牌格式从"无意义的随机字符串(不透明令牌)"升级为了"自包含且可被校验的数据载体"。

1. 传统不透明令牌 (Opaque Token) vs JWT

复制代码
sequenceDiagram
    autonumber
    rect rgb(240, 248, 255)
    note right of 资源服务器: 方案 A: 不透明令牌 (Opaque Token)
    资源服务器->>授权服务器: 令牌内省请求 (Token Introspection)
    授权服务器-->>资源服务器: 返回令牌状态与用户信息 (有网络开销)
    end
    
    rect rgb(245, 245, 220)
    note right of 资源服务器: 方案 B: JWT 令牌
    资源服务器->>资源服务器: 使用本地缓存的公钥解密并验签 (无网络开销)
    end

sequenceDiagram autonumber rect rgb(240, 248, 255) note right of 资源服务器: 方案 A: 不透明令牌 (Opaque Token) 资源服务器->>授权服务器: 令牌内省请求 (Token Introspection) 授权服务器-->>资源服务器: 返回令牌状态与用户信息 (有网络开销) end rect rgb(245, 245, 220) note right of 资源服务器: 方案 B: JWT 令牌 资源服务器->>资源服务器: 使用本地缓存的公钥解密并验签 (无网络开销) end

  • 不透明令牌(Opaque Token):资源服务器收到令牌后,无法直接解读其内容,必须每次都向授权服务器发起网络请求进行"令牌内省(Introspection)"。这会造成授权服务器的巨大并发压力。
  • JWT 令牌(Self-Contained Token) :JWT 内部携带了用户 ID、过期时间、权限范围(Scopes)等信息。资源服务器只需要在启动时或定期获取授权服务器的 JWKS(公钥集) ,即可在本地直接进行签名校验 and 解码,实现了完全无状态的去中心化校验,极大地优化了系统性能。

四、 JWT 在 OAuth 2.0 与 OpenID Connect (OIDC) 中的具体应用

JWT 并非只扮演"访问令牌"这一种角色。在现代安全架构中,它的职责非常多元:

1. OAuth 2.0 中的应用:JWT Access Token

虽然 OAuth 2.0 规范本身没有规定 Access Token 的具体格式,但在实际落地中,大家普遍达成共识使用 JWT 格式。它作为客户端请求受保护资源的通行证,具备自包含、防篡改和易校验的特性。

2. OIDC (OpenID Connect) 中的全面应用

OIDC 是在 OAuth 2.0 之上构建的身份认证层。如果说 OAuth 2.0 解决的是"授权(Authorization)"问题,那么 OIDC 解决的就是"认证(Authentication / 我是谁)"问题。在这个体系中,JWT 是绝对的核心:

  • ID Token(身份令牌)
    • 必须是标准的 JWT 格式
    • 当用户登录成功后,授权服务器会同时颁发 ID Token 和 Access Token。
    • ID Token 包含关于用户身份的基本 Claim(如 sub 用户唯一标识、iss 签发者、auth_time 认证时间等),客户端可以安全地解析它来获取当前登录用户的基本信息,用于前端渲染或本地会话初始化。
  • UserInfo Response(用户信息响应)
    • 客户端请求 /userinfo 接口时,传统的响应是普通 JSON。但在更高安全要求的场景下,授权服务器可以返回一个经过签名甚至加密的 JWT 格式的用户信息(Signed/Encrypted JWT),确保用户信息在传输过程中不被窃听或篡改。

五、 实战演练:如何实现"用户无感知"的授权码模式改造?

对于企业或政务门户系统,开发者最担心的就是:"改造成授权码模式后,用户体验会不会变差?用户是不是必须要面对一个简陋的系统登录页?"

答案是:完全不会!对普通用户而言,改造前后几乎没有任何视觉和操作上的区别。

1. 用户视角的微小变化(实际体验升级)

  • 视觉界面完全一致:用户打开系统,看到的依然是原汁原味的蓝色背景图、业务功能气泡和白色登录框。
  • 地址栏的微调 :浏览器地址栏会短暂发生跳转(例如从 app.com/login 切换到统一认证域名 auth.com/login)。
  • 单点登录(SSO)体验 :如果用户已经在当前浏览器登录了系统 A,再次打开同生态的系统 B 时,系统将自动免密登录,无需用户二次输入。

2. 技术层面的实现步骤

要在 Spring Authorization Server 中使用我们自定义的精美登录页面,只需以下三步:

第一步:在 Security 配置中开启自定义登录路由

在授权服务器的 SecurityFilterChain 配置中,指定使用 /login 路由作为登录入口,并允许所有人匿名访问:

复制代码
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated()
        )
        // 关键点:启用自定义登录页面
        .formLogin(formLogin -> formLogin
            .loginPage("/login")
            .permitAll()
        );
    return http.build();
}

@Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) // 关键点:启用自定义登录页面 .formLogin(formLogin -> formLogin .loginPage("/login") .permitAll() ); return http.build(); }

第二步:在 Controller 中映射 /login 请求

写一个常规的 Spring Controller,将 /login 请求路由到你的 HTML 模板(如 Thymeleaf 模板):

复制代码
@Controller
public class LoginController {

    @GetMapping("/login")
    public String loginPage() {
        // 返回 templates/login.html
        return "login"; 
    }
}

@Controller public class LoginController { @GetMapping("/login") public String loginPage() { // 返回 templates/login.html return "login"; } }

第三步:迁移前端 HTML/CSS 样式并遵循 Security 规范

将你原有的前端登录页面代码拷贝到 resources/templates/login.html 中,并对表单(Form)做以下适配以兼容 Spring Security:

  1. 表单 Action :指定 POST 提交到 /login

  2. 表单 Input 参数名 :账号输入框设置 name="username",密码输入框设置 name="password"

  3. 加入 CSRF 防御 Token :在 Thymeleaf 表单中加入隐藏域,防止跨站请求伪造:

    复制代码
    <form action="/login" method="post">
        <!-- 1. 账号密码输入框 -->
        <input type="text" name="username" placeholder="请输入用户名" />
        <input type="password" name="password" placeholder="请输入密码" />
        
        <!-- 2. 注入 CSRF Token (安全必须项) -->
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
        
        <button type="submit">登 录</button>
    </form>

通过这种方式,底层的核心安全逻辑(Token 颁发、安全拦截、CSRF 防御、会话共享)全部由 Spring Security 官方托管,而界面的控制权依然牢牢掌握在你的手中。


六、 总结:如何构建现代化的 Spring 安全架构?

  1. 废弃旧依赖 :全面停止引入 spring-security-oauth,避免依赖冲突与安全隐患。
  2. 拥抱新架构
    • 使用 Spring Boot 3 + Spring Security 6 来配置你的微服务网关 and 资源服务器。
    • 使用 Spring Authorization Server 构建独立集中的认证中心。
  3. 安全选型
    • 前后端分离项目与移动端推荐使用 Authorization Code + PKCE 模式。
    • 彻底摒弃密码模式
    • 全面采用 JWT 格式的 Access Token 和 ID Token,结合 JWKS 实现高效的分布式本地验签。

作者注:安全是一个动态演进的过程。从 Opaque Token 到 JWT,从 OAuth 2.0 到 OAuth 2.1,每一次规范的收紧都是为了应对日益复杂的网络安全威胁。紧跟 Spring 官方的最佳实践,才能让我们开发的应用坚如磐石。