Ohhnews

分类导航

$ cd ..
DZone Java原文

Java应用中OAuth 2.0 On-Behalf-Of(OBO)流程:代表用户安全调用下游API

#oauth 2.0#obo流程#java#spring boot#安全授权

现代企业应用很少独立运行。用户可能通过 Web 或移动应用进行身份验证,调用基于 Java 的后端 API,而后端又可能需要调用额外的下游服务(如微服务或第三方 API)。在这些场景中,仅使用应用自身的身份往往不够。下游服务可能需要知道是谁发起了请求,并基于该用户的权限进行授权。这正是 OAuth 2.0 On-Behalf-Of (OBO) 流程的价值所在。本文将总结 OBO 流程的工作原理、在现代 Java 架构中的适用场景,以及如何在 Spring Boot 应用中安全地实现它。

每个下游服务如何知道原始用户是谁?

许多工程师首先想到的是:后端可以直接复用自身的应用凭据与其他服务通信。这在机器对机器通信中可行,但一旦需要基于用户进行授权,就力不从心了。考虑一个医疗应用场景:医生登录患者门户并请求病历。初始的 Java API 验证了请求,但获取病历可能需要调用另一个负责患者信息的内部 API。该下游 API 需要知道是哪个医生发起了请求,才能决定是否授予访问权限。如果 Java 后端只使用自身的应用身份,下游服务就会丢失用户上下文,无法基于医生的权限进行授权。这正是 OAuth 2.0 On-Behalf-Of (OBO) 流程要解决的问题。

什么是 OBO(On-Behalf-Of,代表用户)流程?

OBO 流程允许中间层服务(API A)为另一个下游服务(API B)获取访问令牌,同时保留已登录用户的身份和权限。API A 不是用自身的应用凭据调用 API B,而是将用户的访问令牌交换为针对 API B 的新令牌。流程如下:

用户
|
v
Web/移动应用
|
| 访问令牌
v
Java API A
|
| OBO 令牌交换
v
身份提供者
|
| 新访问令牌
v
Java API B

结果,API B 收到代表实际用户的令牌,从而能够执行正确的授权检查。

为什么不使用客户端凭据(Client Credentials)?

许多开发者在调用下游 API 时错误地使用了客户端凭据流程。虽然客户端凭据适用于服务间通信,但它不携带用户上下文。以我们的医疗应用为例:

  • 史密斯医生登录患者门户。
  • Java API 从另一个服务获取患者记录。
  • 下游服务必须验证史密斯医生的权限。

如果使用客户端凭据,下游服务只能看到应用身份,无法了解实际发起请求的用户。OBO 通过保留委托权限解决了这个问题。

典型企业用例

OBO 常用于:

  • 医疗应用中访问患者记录
  • 企业微服务
  • 多层 API 架构
  • 内部服务授权
  • 审计与合规要求

许多实施零信任架构的组织都严重依赖 OBO 这样的委托授权模型。

在 Spring Boot 中实现 OBO

假设:

  • Microsoft Entra ID(Azure AD)为身份提供者。
  • API A 是一个 Spring Boot 应用。
  • API B 是一个下游服务。

步骤 1:添加 MSAL4J 依赖

$ xml
<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>msal4j</artifactId>
    <version>1.15.0</version>
</dependency>

步骤 2:代表用户获取令牌

从前端应用接收传入的访问令牌。

$ java
String clientId = "YOUR_CLIENT_ID";
String clientSecret = "YOUR_CLIENT_SECRET";
IClientCredential credential = ClientCredentialFactory.createFromSecret(clientSecret);
ConfidentialClientApplication app = ConfidentialClientApplication.builder(
        clientId, credential)
        .authority("https://login.microsoftonline.com/TENANT_ID")
        .build();

UserAssertion userAssertion = new UserAssertion(incomingUserToken);
OnBehalfOfParameters parameters = OnBehalfOfParameters.builder(
        Collections.singleton("api://api-b/.default"), userAssertion)
        .build();

IAuthenticationResult result = app.acquireToken(parameters).join();
String downstreamAccessToken = result.accessToken();

至此,Java 应用已获取了一个新令牌,可用于调用 API B,同时保留用户身份。

步骤 3:调用下游 API

使用 Spring 的 RestTemplate:

$ java
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(downstreamAccessToken);
HttpEntity<String> request = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(
        "https://api-b.company.com/patients",
        HttpMethod.GET,
        request,
        String.class);
return response.getBody();

现在 API B 收到一个代表已认证用户的委托令牌。

安全最佳实践

正确实现 OBO 至关重要。

1. 验证传入的令牌

始终验证:

  • 签名
  • 颁发者
  • 受众
  • 过期时间

切勿信任未经验证的客户端令牌。

2. 最小权限原则

仅请求下游 API 所需的作用域。

错误:https://graph.microsoft.com/.default

更好:

$ plaintext
User.Read

限制作用域可降低令牌泄露时的风险范围。

3. 切勿记录访问令牌

避免:logger.info(token);

访问令牌通常包含敏感声明和权限。

4. 保护客户端密钥

将密钥存储在:

  • Azure Key Vault
  • AWS Secrets Manager
  • HashiCorp Vault

切勿将密钥存储在 application.properties 或源代码仓库中。

5. 实现令牌缓存

重复获取令牌会造成不必要的延迟。考虑缓存 OBO 令牌直至过期。大多数企业身份库已提供令牌缓存支持。

常见错误

我在指导新开发者时常遇到以下问题:

  • 使用客户端凭据代替 OBO
  • 将用户令牌直接传递给下游 API
  • 请求过多作用域
  • 记录 JWT 令牌
  • 未验证令牌受众
  • 硬编码客户端密钥

这些问题常导致授权失败或安全漏洞。

结论

随着组织采用微服务和 API 优先架构,跨服务边界保留用户身份变得越来越重要。OAuth 2.0 On-Behalf-Of 流程提供了一种安全且基于标准的方法,使 Java 应用能在调用下游 API 时保持原始用户的上下文和权限。通过正确实现 OBO,开发者可以构建更安全、可审计且符合现代零信任安全原则的应用。对于企业级 Java 团队而言,理解 OBO 已不再是可选项,而是构建安全分布式系统的基本要求。

DZone 贡献者的观点仅代表其个人立场。