【面试突击】Spring Security + OAuth2 密码模式实战:Gateway 作为网关与资源服务器,Auth 作为认证服务器(完整认证链路解析)

文章目录

    • [1. 架构与职责划分](#1. 架构与职责划分)
      • [1.1 Auth 服务(认证服务器)](#1.1 Auth 服务(认证服务器))
      • [1.2 Gateway(网关 + 资源服务器)](#1.2 Gateway(网关 + 资源服务器))
    • [2. 登录请求形态(Password 模式)](#2. 登录请求形态(Password 模式))
    • [3. Auth 服务内部:从 Basic 校验到签发 JWT 的完整链路](#3. Auth 服务内部:从 Basic 校验到签发 JWT 的完整链路)
      • [3.1 Basic 校验:进入 `BasicAuthenticationFilter`(发生在 Auth)](#3.1 Basic 校验:进入 BasicAuthenticationFilter(发生在 Auth))
      • [3.2 进入 Token 端点:`/oauth/token` 封装用户登录凭证](#3.2 进入 Token 端点:/oauth/token 封装用户登录凭证)
      • [3.3 `ProviderManager` 循环调用 `AuthenticationProvider`,命中自定义 Provider](#3.3 ProviderManager 循环调用 AuthenticationProvider,命中自定义 Provider)
      • [3.4 调用 `UserDetailsService#loadUserByUsername` 加载用户信息](#loadUserByUsername` 加载用户信息)
      • [3.5 签发 Token:Access Token 为 JWT,同时写入 Redis](#3.5 签发 Token:Access Token 为 JWT,同时写入 Redis)
    • [4. 前端:缓存 Token 并在后续请求携带](#4. 前端:缓存 Token 并在后续请求携带)
    • [5. 资源访问链路:Gateway 远程校验(用于吊销)→ 放行并分发 → 透传用户信息](#5. 资源访问链路:Gateway 远程校验(用于吊销)→ 放行并分发 → 透传用户信息)
      • [5.1 Gateway:扩展 `RemoteTokenServices` 调用 `/oauth/check_token`](#5.1 Gateway:扩展 RemoteTokenServices 调用 /oauth/check_token)
      • [5.2 Gateway:解析用户信息并写入请求头透传给下游](#5.2 Gateway:解析用户信息并写入请求头透传给下游)
    • [6. 全链路总结(按执行顺序)](#6. 全链路总结(按执行顺序))

本文分享一套在微服务体系中落地认证与鉴权的实现方式:使用 Spring Security + OAuth2(Password密码模式) ,由 Auth 服务 作为认证服务器(Authorization Server),由
Gateway 同时承担 网关资源服务器 (Resource Server)的职责。核心目标是把链路讲清楚:从「前端登录」到「签发 JWT 并写入 Redis」再到「网关校验
Token、透传用户信息」的完整流程,并把 关键过滤器ProviderUserDetailsServiceTokenStore
的调用顺序按实际执行顺序梳理出来,便于理解与排查问题。


适用范围说明(很重要)

本文的实现方式基于 Password Grant(密码模式) 。该模式在 OAuth2.1

中已不再推荐,但在企业内部系统里,如果属于自家第一方应用(前端与后端同属一个安全域、可控且强信任),仍然有较多落地案例。本文聚焦工程实现与链路剖析,不讨论协议演进取舍。


1. 架构与职责划分

1.1 Auth 服务(认证服务器)

Auth 服务提供两类能力:

  • 签发 Token
    • POST /oauth/token:密码模式换取 Token
  • 校验 Token
    • POST /oauth/check_token:供资源服务器远程校验使用(Token Introspection)

在 Auth 服务中会做一些定制化扩展(不同团队会略有差异):

  • 自定义 Client 认证 Provider (例如 ClientAuthenticationProvider):用于 Basic 方式校验 clientId/clientSecret
  • 自定义 UserDetailsService#loadUserByUsername:加载用户信息
  • 自定义 TokenStore:将 Token 数据写入 Redis(用于会话管理、吊销、风控扩展等)

关键点:Basic 认证发生在 Auth 服务。Gateway 只负责转发登录请求,不承担 client 认证。

1.2 Gateway(网关 + 资源服务器)

Gateway 主要承担三件事:

  • 统一入口与路由转发
    • 登录请求:前端先请求 Gateway,由 Gateway 转发到 Auth 的 /oauth/token
  • 资源服务器鉴权
    • 业务请求携带 Token 到达 Gateway 后,Gateway 先校验 Token,再决定是否分发
  • 用户上下文透传
    • 在 Gateway 内通过过滤器/拦截器从 JWT(或校验结果)中提取用户 ID 等信息,写入请求头透传给下游微服务

2. 登录请求形态(Password 模式)

登录时,前端会向 Gateway 发起请求(随后由 Gateway 转发到 Auth),典型请求如下:

  • Header:

    • Authorization: Basic base64(clientId:clientSecret)
    • Content-Type: application/x-www-form-urlencoded
  • Body(表单):

    • grant_type=password
    • username=xxx
    • password=yyy
    • (可选)scope=all

3. Auth 服务内部:从 Basic 校验到签发 JWT 的完整链路

下面按实际执行顺序梳理 Auth 服务内发生的事情。

3.1 Basic 校验:进入 BasicAuthenticationFilter(发生在 Auth)

请求头带了:

复制代码
Authorization: Basic base64(clientId:clientSecret)

因此会先经过 BasicAuthenticationFilter。该过滤器解析 Basic 头后,会把 OAuth2 Client 的认证请求交给 Spring Security 的认证体系处理。

这一阶段的目标是:认证 OAuth2 Client 是否合法(不是认证业务用户)。

此处通过自定义的 ClientAuthenticationProvider 完成 clientId/clientSecret 校验(可对接数据库、缓存或配置中心)。

整体过程可以概括为:

  1. BasicAuthenticationFilter 解析 Basic Header
  2. 构造 Authentication 请求对象(具体类型取决于实现)
  3. 调用 AuthenticationManager.authenticate(...) 完成 Client 认证

3.2 进入 Token 端点:/oauth/token 封装用户登录凭证

当 Client 认证通过后,请求进入 OAuth2 的 Token Endpoint:/oauth/token

当请求参数为 grant_type=password 时,框架会读取 usernamepassword,并封装为:

  • UsernamePasswordAuthenticationToken

随后调用:

  • AuthenticationManager.authenticate(usernamePasswordAuthenticationToken)

默认的 AuthenticationManager 实现通常是:

  • ProviderManager

3.3 ProviderManager 循环调用 AuthenticationProvider,命中自定义 Provider

ProviderManager 内部维护一组 AuthenticationProvider,会按顺序循环执行:

  • supports(authentication.getClass())
  • 若支持则调用 authenticate(authentication)

最终会命中自定义的用户认证 Provider(或基于 DaoAuthenticationProvider 扩展的实现),进入用户认证逻辑。


3.4 调用 UserDetailsService#loadUserByUsername 加载用户信息

在自定义 Provider 中会调用:

  • UserDetailsService#loadUserByUsername(username)

由该方法完成用户信息加载(查库/缓存/远程服务均可),返回 UserDetails。随后结合 PasswordEncoder 完成密码比对,并检查账户状态(是否锁定、是否过期、是否启用等)。

认证通过后,会生成一个 authenticated=trueAuthentication,其中包含用户主体信息与权限集合(GrantedAuthorities)。


3.5 签发 Token:Access Token 为 JWT,同时写入 Redis

用户认证成功后,OAuth2 进入 Token 签发流程:

  • 生成 OAuth2AccessToken(以及可选的 Refresh Token)
  • 通过自定义 TokenStore 写入 Redis

这里的关键约定是:

  • 返回给前端的 access_token 是 JWT,其中会包含部分用户字段(例如用户 ID、用户名等 claims)
  • Redis 扮演"服务端会话/状态"的角色 :用于存储"全量 token 或 token 索引信息",以支持后续的主动吊销、强制下线、黑名单等能力

典型响应结构如下:

json 复制代码
{
  "access_token": "xxx.yyy.zzz",
  "token_type": "bearer",
  "refresh_token": "yyy",
  "expires_in": 43199,
  "scope": "all"
}

4. 前端:缓存 Token 并在后续请求携带

前端拿到 access_token 后,会写入 sessionStorage(或 localStorage),后续访问业务接口时统一携带:

复制代码
Authorization: Bearer <access_token>

5. 资源访问链路:Gateway 远程校验(用于吊销)→ 放行并分发 → 透传用户信息

5.1 Gateway:扩展 RemoteTokenServices 调用 /oauth/check_token

业务请求到达 Gateway 后,Gateway 作为资源服务器需要先校验 Token。

这里采用 远程校验(Token Introspection)

  • 在 Gateway 中扩展/重写 RemoteTokenServices
  • 调用 Auth 的 POST /oauth/check_token 校验 token 是否有效

之所以在 access_token 已经是 JWT 的情况下仍然保留 /check_token,核心目的是引入服务端可控性

即使 JWT 在结构上仍然"可验签且未过期",也可以通过 Redis/服务端状态实现主动吊销(例如注销、踢下线、密码修改后使旧 token 失效等)。

注意:/oauth/check_token 通常也需要 client 认证(Basic)。因此 Gateway 调用该接口时一般也需要携带 client 的 Basic 认证信息,避免把 introspection 接口暴露为匿名可用。


5.2 Gateway:解析用户信息并写入请求头透传给下游

Gateway 内会实现拦截器/过滤器,用于:

  • 从 JWT(或 /check_token 返回的 claims)中提取:

    • 用户 ID
    • 用户名
    • 租户 ID
    • 角色/权限等
  • 写入请求头透传到下游微服务,例如:

    X-User-Id: 10001
    X-Username: sfsfsfsdf
    X-Tenant-Id: t001

同时会做一个重要约束:网关负责清洗并覆盖这些内部请求头 ,避免客户端伪造 X-User-Id 等字段;下游服务只信任来自网关的内网请求。


6. 全链路总结(按执行顺序)

  • 登录链路

    前端请求 Gateway → Gateway 转发 Auth

    → Auth:BasicAuthenticationFilter 校验 Client(自定义 ClientAuthenticationProvider

    → 进入 /oauth/token

    → 封装 UsernamePasswordAuthenticationToken

    AuthenticationManager.authenticate(...)ProviderManager

    → 命中自定义用户 Provider

    UserDetailsService#loadUserByUsername

    → 签发 JWT(自定义 TokenStore 写入 Redis)

    → 返回 Token 给前端

  • 访问链路

    前端携带 Authorization: Bearer <token> 请求 Gateway

    → Gateway:RemoteTokenServices 调 Auth /oauth/check_token 远程校验(用于支持吊销/强制失效)

    → 校验通过后放行

    → Gateway 提取用户信息写入请求头

    → 分发到下游微服务

相关推荐
好好沉淀9 小时前
1.13草花互动面试
面试·职场和发展
廋到被风吹走11 小时前
【Spring】Spring Cloud 分布式事务:Seata AT/TCC/Saga 模式选型指南
分布式·spring·spring cloud
阿蒙Amon12 小时前
C#每日面试题-常量和只读变量的区别
java·面试·c#
程序员小白条12 小时前
面试 Java 基础八股文十问十答第八期
java·开发语言·数据库·spring·面试·职场和发展·毕设
xlp666hub14 小时前
Linux 设备模型学习笔记(1)
面试·嵌入式
南囝coding15 小时前
CSS终于能做瀑布流了!三行代码搞定,告别JavaScript布局
前端·后端·面试
踏浪无痕15 小时前
Go 的协程是线程吗?别被"轻量级线程"骗了
后端·面试·go
一只叫煤球的猫16 小时前
为什么Java里面,Service 层不直接返回 Result 对象?
java·spring boot·面试
求梦82016 小时前
字节前端面试复盘
面试·职场和发展
C雨后彩虹17 小时前
书籍叠放问题
java·数据结构·算法·华为·面试