三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

OAuth 2.1授权架构实战:AS/RS分离设计与微服务安全

OAuth 2.1授权架构实战:AS/RS分离设计与微服务安全

1. 项目概述:从单体授权到分离架构的演进之路

在构建现代应用安全体系时,授权(Authorization)是绕不开的核心议题。过去,我们常常将认证(Authentication)和授权逻辑与业务代码紧密耦合,或者使用一个庞大的、一体化的授权服务器(Authorization Server, AS)来包办一切。这种模式在微服务、多租户、分布式系统成为主流的今天,显得越来越笨重和脆弱。一个服务的认证逻辑变更,可能引发整个授权体系的连锁反应;一个租户的权限模型调整,可能需要全站停机更新。这正是“OAuth 2.1 与授权架构 —— AS/RS 分离的正确姿势”这个标题背后所指向的深层痛点:如何设计一个既能满足复杂业务需求,又能保持高可用、易扩展、好维护的授权体系。

OAuth 2.1 作为 OAuth 2.0 的安全增强版,不仅修复了已知的安全漏洞,其设计哲学也更加强调模块化和清晰的职责边界。其中,将授权服务器(AS)与资源服务器(RS)进行彻底分离,正是实现这一哲学的关键技术路径。这不仅仅是部署上的分离,更是逻辑、数据、乃至团队职责的分离。AS 专注于“颁发令牌”这一件事:验证用户身份、确认授权范围、生成并签名令牌。而 RS 则专注于“校验令牌并授权访问资源”:它无需理解复杂的认证流程,只需信任来自 AS 的令牌,并依据令牌中的声明(Claims)做出访问控制决策。

这种分离带来的好处是实实在在的。想象一下,你的用户认证系统需要从数据库迁移到第三方身份提供商(如企业微信登录),你只需要在 AS 侧进行适配,所有依赖该 AS 令牌的资源服务器都无需任何改动。再比如,你需要为不同的业务线(如电商、内容、金融)设计截然不同的权限模型,你可以部署多个轻量级的 RS,它们共享同一个权威的 AS,各自实现复杂的业务权限逻辑,而 AS 保持稳定和纯粹。这种架构的弹性,是单体授权系统难以企及的。

接下来,我将结合自己多年在身份与访问管理(IAM)领域的实战经验,为你彻底拆解 AS/RS 分离架构的设计思路、核心组件、实操步骤以及那些只有踩过坑才知道的注意事项。无论你是在规划一个新系统的授权体系,还是正在为遗留系统的授权混乱而头疼,这篇文章都将提供一套可直接落地的参考方案。

2. 架构核心:深入理解 AS 与 RS 的职责边界

要实现正确的分离,首先必须像宪法界定三权一样,清晰无误地划定 AS 和 RS 的职权范围。任何模糊地带都是未来系统腐化和维护噩梦的源头。

2.1 授权服务器(AS)的单一职责:令牌的颁发者与验证者

AS 的核心使命是作为系统中受信任的第三方,负责颁发代表用户授权意愿的访问令牌(Access Token)。它的所有工作都围绕令牌的生命周期展开。

1. 端点(Endpoint)暴露与管理:一个标准的 AS 至少需要提供以下端点:

  • /authorize:授权端点,处理用户的交互式登录和授权同意。这是 OAuth 流程的起点,通常涉及重定向和会话管理。
  • /token:令牌端点,用于通过授权码(Authorization Code)、客户端凭证(Client Credentials)等授权类型交换访问令牌和刷新令牌。这是 AS 最核心的接口。
  • /jwks:提供 JSON Web Key Set,公开用于验证令牌签名(如 RS256 算法)的公钥。这是 RS 能够信任 AS 所颁发令牌的技术基础。
  • /userinfo(可选但推荐):用户信息端点,RS 或其他客户端可以使用有效的访问令牌从此端点获取用户的标准身份信息(如 sub, name, email)。这有助于 RS 获取基础身份数据,而无需在令牌中携带过多隐私信息。
  • /introspect(可选):令牌内省端点,允许资源服务器提交一个令牌,由 AS 返回该令牌的当前状态(是否有效、过期时间、授权范围等)。这是一种集中式的令牌验证方式,适用于对实时吊销要求极高的场景。

2. 客户端(Client)注册与凭证管理:AS 需要维护所有注册客户端的清单,包括客户端ID、密钥、重定向URI、授权类型、授权范围等。客户端凭证的安全存储(如使用加盐哈希)和传输(必须使用TLS)是 AS 安全的第一道防线。

3. 用户认证与同意:AS 集成了实际的用户认证手段(可能是用户名密码、社交登录、短信验证码等)。在授权码流程中,AS 负责引导用户完成认证,并清晰地展示请求的权限范围(Scopes),获取用户的明确同意(Consent)。这个“同意记录”是审计的关键依据。

4. 令牌的生成与签名:AS 根据认证和授权结果,生成结构化的令牌。目前 JWT(JSON Web Token)是事实标准。AS 使用自己的私钥对 JWT 进行签名(例如使用 RS256 算法),确保令牌的完整性和来源可信。令牌的 payload 部分应包含必要的最小声明集,如iss(签发者,即 AS 自己)、sub(用户标识)、aud(受众,通常为 RS 的标识符)、exp(过期时间)、scope(授权范围)。

注意:AS 绝对不应该包含任何业务逻辑或业务数据。它的世界里只有“用户”、“客户端”、“令牌”和“授权范围”。它不关心“订单”、“文章”或“财务报表”。一旦 AS 开始根据用户角色返回不同的业务字段,分离就失败了。

2.2 资源服务器(RS)的专注职责:令牌的消费者与资源的守卫

RS 的职责相对纯粹:保护它管辖下的 API 或数据资源。它不关心用户是怎么登录的,只关心“持票人”是否有权访问特定资源。

1. 令牌的接收与验证:RS 从收到的 HTTP 请求(通常在Authorization: Bearer <token>头中)提取访问令牌。随后,它必须对令牌进行一系列严格的验证:

  • 结构验证:令牌是否是格式正确的 JWT?
  • 签名验证:使用从 AS 的/jwks端点获取的公钥验证签名,确保令牌确实由可信的 AS 签发且未被篡改。
  • 标准声明验证
    • iss(签发者)是否与信任的 AS 标识符匹配?
    • aud(受众)是否包含本 RS 的标识符?这是防止令牌被滥用至非目标服务的关键检查。
    • exp(过期时间)是否已过期?
    • nbf(生效时间,如果有)是否已生效?
  • 业务声明验证(可选):检查scope是否包含访问当前端点所需的权限范围(例如read:orders)。

2. 访问控制决策:令牌验证通过后,RS 需要基于令牌中的信息(主要是subscope)以及请求的上下文(如请求的URL路径、HTTP方法、请求的资源ID),执行具体的授权逻辑。

  • 基于范围的授权(Scope-Based):这是最常用的一层。例如,一个拥有write:articles范围的令牌可以调用POST /api/articles,但不能调用GET /api/financial-reports(后者可能需要read:reports范围)。这通常在 API 网关或全局过滤器中实现。
  • 基于资源的授权(Resource-Based):这是更细粒度的控制。例如,用户A(sub=user_a)可以GET /api/articles/123,但用户B不能,因为文章123的作者是用户A。这种逻辑强烈依赖于业务数据,必须在 RS 的业务逻辑层内部实现。RS 需要根据sub去查询数据库,判断用户与资源的关系。

3. 与 AS 的协作(可选):对于某些高级场景,RS 可能需要与 AS 进行额外交互:

  • 令牌内省:当使用不透明令牌(非JWT)或需要实时检查令牌吊销状态时,RS 可以调用 AS 的/introspect端点。
  • 获取用户信息:如果 JWT 中携带的信息不足,RS 可以调用 AS 的/userinfo端点,使用访问令牌获取更完整的用户档案。

清晰的边界带来的直接好处就是独立部署与扩展。AS 作为安全核心,访问模式相对稳定,可以侧重于安全加固、高可用和审计。而各个 RS 可以根据其业务负载特性独立进行伸缩。电商订单服务(RS)在“双十一”期间可以扩容到100个实例,而用户信息服务(另一个RS)可能只需要10个实例,它们都向同一个 AS 集群验证令牌,互不干扰。

3. 技术选型与核心组件实战

理论清晰后,我们需要选择合适的工具来实现这套架构。市面上有成熟的开源解决方案,也有商业产品,对于大多数团队,从开源方案开始是性价比最高的选择。

3.1 授权服务器(AS)选型:IdentityServer vs Keycloak

这是最关键的决策点之一。两个主流开源方案是 .NET 系的IdentityServer和 Java 系的Keycloak

IdentityServer (Duende IdentityServer)

  • 定位:一个高度可定制、库级别的 OAuth 2.1/OpenID Connect 框架。
  • 优点
    • 与 ASP.NET Core 生态无缝集成,对于 .NET 技术栈团队来说开发体验极佳。
    • 代码级控制,你可以深入到流程的每一个细节进行定制,非常适合有复杂定制化需求的场景(如特殊的授权流程、自定义令牌格式)。
    • 文档清晰,社区活跃。
  • 缺点
    • 需要自行实现用户界面(登录页、同意页)、用户存储(通常是数据库)和客户端配置存储。它提供了构建 AS 所需的所有“零件”,但你需要自己“组装成车”。对于生产环境,你需要考虑持久化、集群、缓存等一系列问题。
    • Duende 版本在商业应用上有授权许可要求,需要留意其商业政策。
  • 适合场景:.NET 技术栈为主,对授权流程有深度定制需求,且团队有足够精力进行集成和运维的中大型项目。

Keycloak

  • 定位:一个开箱即用的完整身份认证与访问管理解决方案。
  • 优点
    • 开箱即用:自带功能完善的管理控制台,可以通过 UI 配置客户端、用户、角色、领域(Realm)。内置登录、注册、账户管理页面。
    • 功能全面:不仅支持 OAuth 2.1/OIDC,还支持 SAML,自带社交登录(Google, GitHub等)适配器,支持细粒度的基于角色的权限管理(Role-Based Access Control)。
    • 易于集成:对客户端应用友好,提供多种语言的适配器(Adapter)。作为 RS,你通常只需要配置一个连接器即可。
    • 集群、持久化等生产级特性原生支持。
  • 缺点
    • 相对“重”,定制化不如 IdentityServer 灵活。如果你需要的行为不在其预设范围内,定制开发可能比较麻烦。
    • 默认配置可能较为复杂,学习曲线前期较陡。
  • 适合场景:需要快速搭建一个功能完整、生产可用的 AS,技术栈多样(Java, .NET, Node.js等),且对开箱即用和统一管理有强烈需求的团队。

我的实战建议:如果你的团队技术栈混合,或者希望快速上线、减少在身份认证基础组件上的开发投入,Keycloak 通常是更优选择。它让你能更专注于业务 RS 的开发。而对于深度绑定 .NET、且授权逻辑极为特殊的场景,IdentityServer 提供了无与伦比的灵活性。

3.2 资源服务器(RS)实现:以 ASP.NET Core 为例

无论 AS 选型如何,RS 的实现模式是通用的。在 ASP.NET Core 中,我们可以利用其强大的认证/授权中间件体系。

1. 核心依赖包

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

这个包提供了验证 JWT Bearer Token 的认证处理器。

2. 服务配置(Program.cs 或 Startup.cs)

// 从配置或环境变量获取 AS 的地址和 RS 自身的标识符 var authority = Configuration["OAuth:Authority"]; // 例如:https://auth.yourdomain.com var audience = Configuration["OAuth:Audience"]; // 你的 RS 标识符,必须与 AS 颁发令牌时的 `aud` 匹配 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = authority; options.Audience = audience; // 验证令牌的 `aud` 声明 options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, // 验证签发者 ValidateAudience = true, // 验证受众 ValidateLifetime = true, // 验证有效期 ValidateIssuerSigningKey = true, // 验证签名密钥 // 通常不需要指定 IssuerSigningKey,框架会通过 Authority 自动发现 JWKS 端点获取公钥 }; // 可选:对于内网或特定环境,可能需要禁用 HTTPS 元数据请求(生产环境绝不允许) // options.RequireHttpsMetadata = false; // 可选:设置令牌提取方式(默认从 Authorization 头读取 Bearer Token) // options.Events = new JwtBearerEvents { ... }; }); builder.Services.AddAuthorization(); // 添加授权服务

3. 中间件与端点保护: 在请求管道中启用认证和授权中间件:

app.UseAuthentication(); app.UseAuthorization();

然后在控制器或最小 API 端点中使用特性进行保护:

[ApiController] [Route("api/[controller]")] [Authorize] // 要求请求必须携带有效令牌 public class OrdersController : ControllerBase { [HttpGet("{id}")] [RequiredScope("read:orders")] // 更细粒度:要求令牌必须包含 read:orders 范围 public IActionResult GetOrder(int id) { // 可以通过 User.Identity.Name 或 User.FindFirst(ClaimTypes.NameIdentifier)?.Value 获取 `sub` var userId = User.FindFirst("sub")?.Value; // 结合 userId 和 id 执行基于资源的授权逻辑(例如,检查订单是否属于该用户) // ... return Ok(order); } [HttpPost] [RequiredScope("write:orders")] public IActionResult CreateOrder([FromBody] Order order) { // ... return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order); } }

这里的[RequiredScope]特性需要自定义或使用类似Microsoft.Identity.Web库提供的特性。其原理就是在授权过滤器(Authorization Filter)中检查User.Claims中是否存在对应的scope声明。

3.3 令牌格式与安全:JWT 的细节魔鬼

JWT 是 AS/RS 分离架构中信息传递的载体,其设计和使用中的细节至关重要。

1. 令牌内容设计(Payload): 一个设计良好的 JWT Payload 应该遵循“最小必要”原则。

{ "iss": "https://auth.yourcompany.com", "aud": ["api.orders", "api.users"], // 受众可以是数组,表明此令牌可用于多个RS "exp": 1735689600, "sub": "user-123456", "scope": "read:orders write:orders openid profile", "client_id": "spa-client-app", "auth_time": 1735686000 }
  • 避免放入敏感信息:如密码、完整地址、支付信息等。JWT 默认只进行 Base64 编码,而非加密(除非使用 JWE)。任何拿到令牌的人都可以解码看到 payload 内容。
  • 谨慎使用自定义声明:只添加 RS 进行访问控制所必需的信息。例如,可以添加tenant_id用于多租户隔离,或role用于简单的角色判断(但复杂授权建议基于scopesub在 RS 内查询)。
  • aud声明的意义:这是安全的关键。AS 在颁发令牌时,必须根据客户端请求的 RS API 来正确设置aud。RS 在验证时必须严格检查aud是否包含自己。这能防止一个发给“订单服务”的令牌被意外或恶意用于访问“财务服务”。

2. 签名算法选择

  • HS256(对称加密):使用同一个密钥进行签名和验证。在 AS/RS 分离架构中绝对禁止使用,因为你需要将密钥分发给所有 RS,密钥泄露风险呈指数级增长,且一个 RS 的密钥泄露会危及所有服务。
  • RS256 / ES256(非对称加密):AS 使用私钥签名,RS 使用公钥验证。公钥可以安全地通过 JWKS 端点公开。这是分离架构的唯一推荐选择。RS256 基于 RSA,ES256 基于椭圆曲线 ECDSA,后者密钥更短、性能可能更好,但兼容性略差,RS256 是目前最通用的选择。

3. 令牌生命周期与刷新

  • 访问令牌(Access Token):生命周期应较短,建议 5-15 分钟。这限制了令牌泄露后造成的破坏时间窗口。
  • 刷新令牌(Refresh Token):生命周期较长,如 7 天或更长。用于在访问令牌过期后获取新的访问令牌,而无需用户重新登录。刷新令牌必须被 AS 安全地存储(如数据库),并与客户端和用户绑定,且应具备吊销机制。

4. 令牌存储与传输

  • 前端(SPA、移动App):访问令牌不建议存储在localStoragesessionStorage中,有 XSS 风险。推荐使用内存存储,或使用带有HttpOnlySecureSameSite标记的 Cookie(但需注意 CSRF 防护)。刷新令牌绝对不可以暴露给前端,应仅用于后端机密客户端。
  • 后端 RS 间调用:当一个 RS(如 API Gateway)需要调用另一个 RS(如订单服务)时,它应该使用客户端凭证流(Client Credentials Flow)获取一个代表服务本身(而非用户)的令牌,或者将原始用户令牌(JWT)以“Bearer Token”形式传递给下游服务(即 Token Propagation 模式)。后者更常见,但要求所有下游 RS 都信任同一个 AS。

4. 完整部署与配置流程实录

让我们以一个典型的微服务场景为例,实战演练从零搭建一套 AS/RS 分离的授权体系。假设我们有一个电商系统,包含用户服务、订单服务和商品服务。

4.1 步骤一:部署与配置授权服务器(以 Keycloak 为例)

  1. 启动 Keycloak:使用 Docker 是最快的方式。

    docker run -d \ --name keycloak \ -p 8080:8080 \ -e KEYCLOAK_ADMIN=admin \ -e KEYCLOAK_ADMIN_PASSWORD=your_strong_password \ quay.io/keycloak/keycloak:latest start-dev

    生产环境需要配置数据库(如 PostgreSQL)、设置 HTTPS、配置集群等。

  2. 登录管理控制台:访问http://localhost:8080,使用 admin/your_strong_password 登录。

  3. 创建领域(Realm):领域是隔离租户和应用的顶级容器。点击左上角下拉框,选择 “Create realm”,命名为 “E-Commerce”。

  4. 创建客户端(Client)

    • 在 “E-Commerce” 领域下,进入 “Clients” -> “Create client”。
    • Client IDorder-service(代表我们的订单资源服务器)。
    • Client Protocolopenid-connect
    • 点击 “Save”。
    • 在客户端的设置页中,配置关键项:
      • Access Type:选择confidential(表示这是一个后端服务,能安全保存密钥)。
      • Valid Redirect URIs:对于 RS,通常不需要前端重定向,可以留空或填写一个占位符。如果是 SPA 客户端,则需精确配置其回调地址。
      • Web Origins:根据需要配置 CORS。
      • 保存后,切换到 “Credentials” 标签页,这里会自动生成Client Secret,记录下来,RS 配置时会用到。
  5. 定义客户端作用域(Client Scopes)

    • 进入 “Client scopes”,点击 “Create”。
    • 名称:read:orders,描述:读取订单权限。协议:openid-connect
    • 同样创建write:ordersread:products等。
    • 这些作用域可以在颁发令牌时被请求和授予。
  6. 将作用域关联到客户端

    • 回到order-service客户端的 “Client Scopes” 标签页。
    • 在 “Default Client Scopes” 中,将read:orderswrite:orders添加进去。这意味着当为该客户端颁发令牌时,这些作用域可以作为默认值或可选值。
  7. 配置 Mappers(可选):如果你需要在令牌中添加自定义声明,可以在客户端的 “Mappers” 标签页创建。例如,创建一个 “User Attribute” 类型的 Mapper,将用户的department属性映射到令牌的department声明。

  8. 获取 JWKS 端点地址:AS 的公钥信息通过一个标准的发现端点提供。地址通常是:http://localhost:8080/realms/e-commerce/protocol/openid-connect/certs。这个地址就是 RS 配置中Authority的基础 URLhttp://localhost:8080/realms/e-commerce加上标准路径。

4.2 步骤二:配置订单服务(RS)以验证 Keycloak 令牌

在订单服务的 ASP.NET Core 项目中,进行如下配置:

  1. 安装 NuGet 包:如前所述,安装Microsoft.AspNetCore.Authentication.JwtBearer

  2. appsettings.json 配置

    { "OAuth": { "Authority": "http://localhost:8080/realms/e-commerce", "Audience": "order-service" // 必须与 Keycloak 中创建的 Client ID 完全一致 } }
  3. 服务注册与中间件配置:代码与 3.2 节所示完全一致。框架会自动从{Authority}/.well-known/openid-configuration发现端点获取配置,包括jwks_uri

  4. 测试令牌获取与访问

    • 使用 Postman 或 curl 向 Keycloak 的令牌端点发起请求,模拟客户端获取令牌:
      curl -X POST \ http://localhost:8080/realms/e-commerce/protocol/openid-connect/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'client_id=order-service&client_secret=YOUR_CLIENT_SECRET&grant_type=client_credentials&scope=read:orders'
      (这里使用了客户端凭证流,适用于服务间调用。如果是用户登录,应使用授权码流程。)
    • 从响应中提取access_token
    • 用这个令牌访问订单服务的受保护端点:
      curl -X GET \ http://localhost:5000/api/orders \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
      如果配置正确,订单服务应该返回 200 OK 和订单数据;如果没有令牌或令牌无效,则返回 401 Unauthorized。

4.3 步骤三:实现基于资源的细粒度授权

令牌验证通过只解决了“你是谁”(认证)和“你有什么通用权限”(基于Scope的授权)的问题。要判断“你能否操作这个特定资源”,需要在业务逻辑中实现。

在订单服务的GetOrder(int id)方法中:

[HttpGet("{id}")] [RequiredScope("read:orders")] public async Task<IActionResult> GetOrder(int id) { // 1. 从已验证的令牌中获取用户标识 var userId = User.FindFirst("sub")?.Value; if (string.IsNullOrEmpty(userId)) { return Forbid(); // 理论上不会发生,因为 [Authorize] 已通过 } // 2. 从数据库查询订单 var order = await _orderRepository.GetByIdAsync(id); if (order == null) { return NotFound(); } // 3. 执行基于资源的授权逻辑 // 规则示例:订单只能由其所属用户或管理员访问 bool isOwner = order.UserId == userId; // 假设我们从令牌或通过 UserInfo 端点获取了用户角色(更佳实践是在RS内维护用户-角色映射) bool isAdmin = User.IsInRole("admin"); // 或者检查 `role` 声明 if (!isOwner && !isAdmin) { // 用户无权访问此特定资源 return Forbid(); // 返回 403 Forbidden,与 401 Unauthorized(未认证)区分开 } // 4. 授权通过,返回资源 return Ok(order); }

这种逻辑完全内聚在 RS 内部,与 AS 无关。AS 只负责告诉 RS “来人是 user-123”,而 RS 自己决定 user-123 能看到哪些订单。

5. 生产环境进阶考量与避坑指南

将分离架构投入生产,会面临更多复杂性和挑战。以下是我从多次项目实践中总结的关键点。

5.1 性能、缓存与高可用

  • JWKS 端点缓存:RS 每次验证 JWT 签名时,都需要获取 AS 的公钥。频繁请求/jwks端点会给 AS 带来压力,并增加 RS 的响应延迟。必须在 RS 端实现公钥缓存Microsoft.AspNetCore.Authentication.JwtBearer库内置了缓存机制(通过ConfigurationManager),默认会缓存获取的配置信息。你需要关注缓存过期时间(通常与 Keycloak 的密钥轮换周期相关),确保在 AS 轮换签名密钥后,RS 能及时更新缓存。
  • AS 的高可用:AS 是系统的单点故障源(SPOF)。必须对 AS 进行集群部署。对于 Keycloak,这意味着配置共享的外部数据库(如 PostgreSQL)和负载均衡器。所有 AS 实例共享同一套客户端和用户配置。对于 IdentityServer,需要确保配置数据存储(如IClientStore,IResourceStore)和操作数据存储(如IPersistedGrantStore)使用共享数据库,并考虑分布式缓存(如 Redis)来共享签名密钥材料。
  • RS 的无状态化:得益于 JWT 的自包含性,RS 本身可以设计为无状态的,这非常利于水平扩展。授权决策所需的信息(sub,scope)都在令牌里,业务授权所需的数据则来自独立的数据库或缓存。

5.2 密钥管理、轮换与安全加固

  • 私钥安全:AS 的签名私钥是其生命线。必须存储在安全的硬件模块(HSM)或至少是安全的密钥管理服务(KMS)中。在 Docker/ Kubernetes 环境中,避免将私钥硬编码在镜像或环境变量中,应使用 Secrets 管理工具。
  • 密钥轮换(Key Rotation):定期更换签名密钥是安全最佳实践。Keycloak 和 IdentityServer 都支持自动密钥轮换。关键在于平滑过渡。新密钥生成后,旧密钥(仍在 JWKS 中)应在一段时间内继续有效,以验证那些尚未过期的旧令牌。RS 的 JWKS 缓存机制需要能够自动发现新密钥。
  • 令牌吊销(Token Revocation):JWT 一旦签发,在过期前无法单方面作废,这是其最大缺点。对于需要立即吊销的场景(如用户登出、管理员禁用账户),有几种策略:
    1. 使用短寿命令牌:将访问令牌有效期设为极短(如5分钟),依赖刷新令牌来维持会话。吊销时,只需在 AS 端使刷新令牌失效即可。这是最常用、最简单的方案。
    2. 令牌内省(Token Introspection):RS 每次收到令牌都向 AS 的/introspect端点查询状态。这提供了最强的实时性,但代价是每次 API 调用都增加了一次网络请求,严重牺牲性能和可用性,仅适用于对安全有极端要求的内部管理接口。
    3. 黑名单(Blacklist):AS 维护一个已吊销令牌ID(JTI)的黑名单,并同步给所有 RS(如通过 Redis Pub/Sub)。RS 在验证令牌时额外检查黑名单。这增加了架构复杂性。我的建议是:优先采用“短寿命访问令牌 + 可吊销的刷新令牌”组合,在安全与复杂度之间取得最佳平衡。

5.3 跨服务调用(服务网格集成)

在微服务架构中,一个用户请求可能涉及多个 RS 的链式调用。如何传递用户身份上下文?

  • 模式一:令牌传播(Token Propagation):网关或第一个 RS 将收到的原始 JWT 放在请求头中,传递给下游服务。这是最直接的方式,要求所有下游服务都信任同一个 AS,并能验证该 JWT。务必注意:不要将令牌传递给不受你控制或不需要知道用户身份的外部服务。
  • 模式二:生成新令牌(Token Exchange):网关或中间服务可以使用自己的客户端凭证,向 AS 发起一个“令牌交换”(OAuth 2.0 Token Exchange)请求,为当前用户获取一个专门针对下游服务的新令牌。这提供了更好的审计追踪和权限隔离(新令牌的 scope 可以更受限),但增加了延迟和 AS 的负载。
  • 与服务网格集成:在 Istio、Linkerd 等服务网格中,通常由边车(Sidecar)代理来处理身份传递。你可以配置网格,让它自动将来自入口网关的 JWT 中的用户身份信息(如sub)提取出来,注入到内部服务调用的请求头中(如X-Forwarded-User)。这样业务服务无需再解析 JWT,直接从请求头读取用户ID即可,简化了 RS 的实现。

5.4 监控、日志与审计

  • AS 监控:密切监控 AS 的端点调用频率、响应时间、错误率。异常的令牌请求峰值可能意味着攻击或客户端 bug。监控/token端点的不同授权类型(grant_type)分布。
  • RS 监控:在 RS 的日志中,应记录每个受保护端点的访问,至少包含:时间戳、请求路径、HTTP 方法、用户标识(sub)、客户端标识(client_id)、授权结果(允许/拒绝)。注意不要记录完整的 JWT,因为它是敏感凭证。可以记录 JWT 的签名部分或 JTI。
  • 集中式审计:所有重要的安全事件都应在 AS 端进行集中审计记录,包括:用户登录成功/失败、用户同意授权、令牌颁发、令牌吊销、客户端配置变更等。这些日志应送入 SIEM(安全信息和事件管理)系统进行分析。

分离架构的成功,不仅在于技术组件的正确拼装,更在于对边界和职责的持续坚守。让 AS 安心做它最擅长的“发牌员”,让 RS 专注做它最拿手的“守门人”,整个系统的安全性和可维护性自然会提升到一个新的层次。

← 返回列表