知識庫 / Spring / Spring Cloud RSS 訂閱

向Feign客户端提供OAuth2令牌

Security,Spring Cloud
HongKong
6
12:20 PM · Dec 06 ,2025

1. 概述

OpenFeign 是一個聲明式的 REST 客户端,可用於 Spring Boot 應用程序。假設我們有一個使用 OAuth2 保護的 REST API,並且希望使用 OpenFeign 調用它。在這種情況下,我們需要使用 OpenFeign 提供訪問令牌。

在本教程中,我們將描述 如何為 OpenFeign 客户端添加 OAuth2 支持

2. 服務間身份驗證

服務間身份驗證是 REST API 安全領域一個熱門話題。我們可以使用 mTLS 或 JWT 為 REST API 提供身份驗證機制。然而,OAuth2 協議是保護 API 的既定方案。 假設我們想要使用另一個服務(客户端角色)調用一個安全的服務(服務器角色)。 在這種情況下,我們使用 客户端憑據 grant 類型。 我們通常使用 客户端憑據進行兩個 API 之間或沒有最終用户之間的身份驗證。 下圖顯示了這種 grant 類型中的主要參與者:

在客户端憑據中,客户端服務使用令牌端點從授權服務器獲取訪問令牌。 然後它使用訪問令牌來訪問由資源服務器保護的資源。 資源服務器驗證訪問令牌,如果有效,則服務請求。

2.1. 授權服務器

為了頒發訪問令牌,我們設置一個授權服務器。為了簡化當前設置,我們將使用嵌入在 Spring Boot 應用程序中的 Keycloak。假設我們使用來自 GitHub 的 授權服務器項目

我們將 Access Type 設置為 credential,並啓用 Service Accounts Enabled 選項。然後,我們將 Realm 詳細信息導出為 feign-realm.json,並將 Realm 文件設置在 application-feign.yml 中:

keycloak:
  server:
    contextPath: /auth
    adminUser:
      username: bael-admin
      password: pass
    realmImportFile: feign-realm.json

現在,授權服務器已準備就緒。 我們可以使用 –spring.profiles.active=feign 選項運行應用程序。 由於本教程的重點是 OpenFeign 的 OAuth2 支持,因此我們無需深入研究它。

2.2 資源服務器

現在我們已經配置了授權服務器,接下來我們設置資源服務器。為此,我們將使用 GitHub 上的資源服務器項目。首先,我們將 Payment 類作為資源添加:

public class Payment {

    private String id;
    private double amount;

   // standard getters and setters
}

然後,我們在 PaymentController</em/> 類中聲明一個 API:

@RestController
public class PaymentController {

    @GetMapping("/payments")
    public List<Payment> getPayments() {
        List<Payment> payments = new ArrayList<>();
        for(int i = 1; i < 6; i++){
            Payment payment = new Payment();
            payment.setId(String.valueOf(i));
            payment.setAmount(2);
            payments.add(payment);
        }
        return payments;
    }

}

getPayments() API 返回支付列表。 此外,我們還配置了資源服務器在 application-feign.yml 文件中:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8083/auth/realms/master

現在,getPayments() API 使用 OAuth2 授權服務器進行安全保護,調用此 API 時,必須提供有效的訪問令牌:

curl --location --request POST 'http://localhost:8083/auth/realms/master/protocol/openid-connect/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id=payment-app' \
  --data-urlencode 'client_secret=863e9de4-33d4-4471-b35e-f8d2434385bb' \
  --data-urlencode 'grant_type=client_credentials'

在獲取訪問令牌後,我們將其設置在請求的 Authorization 標頭中:

curl --location --request GET 'http://localhost:8081/resource-server-jwt/payments' \
  --header 'Authorization: Bearer Access_Token' 

現在,我們希望使用 OpenFeign 代替 cURL 或 Postman 來調用安全 API。

3. OpenFeign 客户端

OpenFeign 客户端是與遠程服務交互的核心組件。它允許您使用聲明式的方式(即通過註解)來調用 RESTful 服務。 OpenFeign 負責處理 HTTP 請求的細節,例如序列化/反序列化請求和響應體,以及處理 HTTP 狀態碼。

以下是一些 OpenFeign 客户端的關鍵特性:

  • 聲明式配置: 通過註解(如 @FeignClient)來定義服務接口。
  • 動態代理: OpenFeign 會自動生成 HTTP 客户端,無需手動創建。
  • 請求方法映射: 可以映射服務接口的方法到具體的 HTTP 方法(如 GET、POST、PUT、DELETE)。
  • 請求參數處理: OpenFeign 能夠自動處理請求參數的序列化和反序列化。
  • 錯誤處理: 可以配置 OpenFeign 來處理 HTTP 錯誤狀態碼。

以下是一個使用 OpenFeign 客户端調用 RESTful 服務的示例:

@FeignClient(name = "my-service")
public interface MyServiceClient {

    @GetMapping("/users/{id}")
    User getUserById(@PathVariable("id") Long id);
}

在這個例子中,@FeignClient 註解指定了服務接口的名稱為 "my-service"。@GetMapping 註解映射了 getUserById 方法到 HTTP GET 方法,並使用 @PathVariable 註解來獲取請求參數 id

3.1. 依賴項

為了使用 Spring Cloud OpenFeign 調用安全 API,我們需要在我們的 spring-cloud-starter-openfeign 中添加 pom.xml 文件:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>3.1.0</version>
</dependency>

此外,我們需要將 spring-cloud-dependencies 添加到 pom.xml 中:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>2021.0.0</version>
    <type>pom</type>
</dependency>

3.2. 配置

首先,我們需要將 <em/>@EnableFeignClients</em> 添加到我們的主類中:

@SpringBootApplication
@EnableFeignClients
public class ExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

然後,我們定義 PaymentClient 接口,用於調用 getPayments() API。 此外,我們需要在 PaymentClient 接口中添加 @FeignClient 註解:

@FeignClient(
  name = "payment-client", 
  url = "http://localhost:8081/resource-server-jwt", 
  configuration = OAuthFeignConfig.class)
public interface PaymentClient {

    @RequestMapping(value = "/payments", method = RequestMethod.GET)
    List<Payment> getPayments();
}

我們根據資源服務器的地址設置了 url。在這種情況下,@FeignClient 的主要參數是 configuration 屬性,它支持 OpenFeign 的 OAuth2。之後,我們定義一個 PaymentController 類並將 PaymentClient 注入其中:

@RestController
public class PaymentController {

    private final PaymentClient paymentClient;

    public PaymentController(PaymentClient paymentClient) {
        this.paymentClient = paymentClient;
    }

    @GetMapping("/payments")
    public List<Payment> getPayments() {
        List<Payment> payments = paymentClient.getPayments();
        return payments;
    }
}

4. OAuth2 支持

OAuth2 是一種授權框架,允許第三方應用程序代表用户訪問其資源,而無需共享用户的憑據。它通過使用訪問令牌(Access Token)和刷新令牌(Refresh Token)來實現。

訪問令牌 (Access Token): 訪問令牌是第三方應用程序用於訪問用户資源的憑證。 它的有效期通常有限,過期後需要使用刷新令牌獲取新的訪問令牌。

刷新令牌 (Refresh Token): 刷新令牌用於獲取新的訪問令牌。 它的有效期通常比訪問令牌更長。

OAuth2 流程

OAuth2 授權流程通常包括以下步驟:

  1. 用户授權: 用户授權第三方應用程序訪問其資源。
  2. 授權碼交換 (Authorization Code Grant): 應用程序使用授權碼請求訪問令牌。
  3. 訪問令牌獲取: 應用程序使用授權碼獲取訪問令牌。
  4. 資源訪問: 應用程序使用訪問令牌訪問用户資源。

關鍵概念

  • 客户端 (Client): 發起授權請求的應用程序。
  • 資源所有者 (Resource Owner): 擁有資源的賬户。
  • 客户端註冊應用 (Client Credentials): 客户端用於標識自身的憑證。
  • 授權服務器 (Authorization Server): 頒發訪問令牌和刷新令牌的服務器。

4.1. 依賴關係

為了在 Spring Cloud OpenFeign 中添加 OAuth2 支持,我們需要在 <em>pom.xml</em> 文件中添加 <a href="https://mvnrepository.com/artifact/org.springframework.security/spring-security-oauth2-client">spring-security-oauth2-client</a><a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security">spring-boot-starter-security</a>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.6.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-client</artifactId>
    <version>5.6.0</version>
</dependency>

4.2. 配置

現在,我們想要創建一個配置。其核心思想是獲取並添加訪問令牌到 OpenFeign 請求中。攔截器可以為每個 HTTP 請求/響應執行此任務。添加攔截器是 Feign 提供的有用的功能。我們將使用 RequestInterceptor,它將 OAuth2 訪問令牌注入到 OpenFeign 客户端的請求中,通過添加 Authorization Bearer 頭部來實現。 讓我們定義 OAuthFeignConfig 配置類並定義 requestInterceptor() Bean:

@Configuration
public class OAuthFeignConfig {

    public static final String CLIENT_REGISTRATION_ID = "keycloak";

    private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
    private final ClientRegistrationRepository clientRegistrationRepository;

    public OAuthFeignConfig(OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
      ClientRegistrationRepository clientRegistrationRepository) {
        this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
        this.clientRegistrationRepository = clientRegistrationRepository;
    }

    @Bean
    public RequestInterceptor requestInterceptor() {
        ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(CLIENT_REGISTRATION_ID);
        OAuthClientCredentialsFeignManager clientCredentialsFeignManager =
          new OAuthClientCredentialsFeignManager(authorizedClientManager(), clientRegistration);
        return requestTemplate -> {
            requestTemplate.header("Authorization", "Bearer " + clientCredentialsFeignManager.getAccessToken());
        };
    }
}

<em>requestInterceptor()</em> Bean 中,我們使用 <em>ClientRegistration</em><em>OAuthClientCredentialsFeignManager</em> 類來註冊 oauth2 客户端並從授權服務器獲取訪問令牌。為此,我們需要在我們的 <em>application.properties</em> 文件中定義 <em>oauth2</em> 客户端的屬性:

spring.security.oauth2.client.registration.keycloak.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.keycloak.client-id=payment-app
spring.security.oauth2.client.registration.keycloak.client-secret=863e9de4-33d4-4471-b35e-f8d2434385bb
spring.security.oauth2.client.provider.keycloak.token-uri=http://localhost:8083/auth/realms/master/protocol/openid-connect/token

讓我們創建 OAuthClientCredentialsFeignManager 類並定義 getAccessToken() 方法:

public String getAccessToken() {
    try {
        OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
          .withClientRegistrationId(clientRegistration.getRegistrationId())
          .principal(principal)
          .build();
        OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
        if (isNull(client)) {
            throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
        }
        return client.getAccessToken().getTokenValue();
    } catch (Exception exp) {
        logger.error("client credentials error " + exp.getMessage());
    }
    return null;
}

我們使用 OAuth2AuthorizeRequestOAuth2AuthorizedClient 類來從授權服務器獲取訪問令牌。 現在,對於每個請求,OpenFeign攔截器管理 oauth2 客户端並將訪問令牌添加到請求中。

5. 測試

要測試 OpenFeign 客户端,讓我們創建一個名為 PaymentClientUnitTest 的類:

@RunWith(SpringRunner.class)
@SpringBootTest
public class PaymentClientUnitTest {

    @Autowired
    private PaymentClient paymentClient;

    @Test
    public void whenGetPayment_thenListPayments() {
        List<Payment> payments = paymentClient.getPayments();
        assertFalse(payments.isEmpty());
    }
}

在本次測試中,我們調用了 getPayments() API。背地裏,PaymentClient 通過攔截器連接到 OAuth2 客户端並獲取訪問令牌。

6. 結論

本文介紹了調用安全API所需的環境搭建。隨後,我們配置OpenFeign,通過一個實際示例調用該安全API。為此,我們向OpenFeign添加並配置了攔截器,該攔截器管理OAuth2客户端並將訪問令牌添加到請求中。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.