欢迎光临
我们一直在努力

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

文章目录

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

本文分享一套在微服务体系中落地认证与鉴权的实现方式:使用
Spring Security + OAuth2(Password密码模式),由
Auth 服务作为认证服务器(Authorization Server),由

Gateway同时承担
网关与
资源服务器(Resource Server)的职责。核心目标是把链路讲清楚:从「前端登录」到「签发 JWT 并写入 Redis」再到「网关校验
Token、透传用户信息」的完整流程,并把
关键过滤器、
Provider、
UserDetailsService、
TokenStore
的调用顺序按实际执行顺序梳理出来,便于理解与排查问题。


适用范围说明(很重要)

本文的实现方式基于 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 校验(可对接数据库、缓存或配置中心)。

整体过程可以概括为:

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

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

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

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

    • 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=true 的 Authentication,其中包含用户主体信息与权限集合(GrantedAuthorities)。


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

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

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

    这里的关键约定是:

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

    典型响应结构如下:

    {
    "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 提取用户信息写入请求头 → 分发到下游微服务

    赞(0)
    未经允许不得转载:171主机测评 » 【面试突击】Spring Security + OAuth2 密码模式实战:Gateway 作为网关与资源服务器,Auth 作为认证服务器(完整认证链路解析)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址