1. Обзор
OpenFeign — это декларативный клиент REST, который мы можем использовать в приложениях Spring Boot . Предположим, что у нас есть REST API, защищенный с помощью OAuth2 , и мы хотим вызвать его с помощью OpenFeign. В этой ситуации нам нужно будет предоставить токен доступа с помощью OpenFeign.
В этом руководстве мы расскажем , как добавить поддержку OAuth2 в клиент OpenFeign .
2. Служба для проверки подлинности службы
Проверка подлинности между службами — популярная тема в области безопасности API. Мы можем использовать mTLS или JWT , чтобы обеспечить механизм аутентификации для REST API. Однако протокол OAuth2 по умолчанию является решением для защиты API . Допустим, мы хотим вызвать безопасную службу (роль сервера), используя другую службу (роль клиента). В этом сценарии мы используем тип предоставления учетных данных клиента . Обычно мы используем учетные данные клиента для аутентификации между двумя API или системами без конечного пользователя. На рисунке ниже показаны основные участники этого типа гранта:
В учетных данных клиента служба клиента получает токен доступа с сервера авторизации, используя конечную точку токена. Затем он использует токен доступа для доступа к ресурсам, защищенным сервером ресурсов. Сервер ресурсов проверяет маркер доступа и, если он действителен, обслуживает запрос.
2.1. Сервер авторизации
Настроим сервер авторизации для выдачи токенов доступа. Чтобы упростить задачу, мы будем использовать Keycloak, встроенный в приложение Spring Boot . Предположим, что мы используем проект сервера авторизации, доступный на GitHub . Во- первых, мы определяем клиент платежного приложения в
мастере
области на нашем встроенном сервере Keycloak:
Мы устанавливаем тип доступа
на учетные данные
и включаем параметр «Учетные записи служб включены
». Затем мы экспортируем детали области как feign-realm.json
и устанавливаем файл области в нашем 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
}
Затем мы объявляем API в классе PaymentController
:
@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;
}
}
API getPayments()
возвращает список платежей. Также настраиваем сервер ресурсов в нашем файле application-feign.yml
:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8083/auth/realms/master
Теперь API getPayments()
защищен с использованием сервера авторизации 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'
Теперь мы хотим вызвать безопасный API, используя OpenFeign вместо cURL или Postman .
3. Клиент OpenFeign
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
в pom.xml
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.0</version>
<type>pom</type>
</dependency>
3.2. Конфигурация
Во-первых, нам нужно добавить @EnableFeignClients
в наш основной класс:
@SpringBootApplication
@EnableFeignClients
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
Затем мы определяем интерфейс PaymentClient
для вызова API getPayments()
. Также нам нужно добавить @FeignClient
в наш интерфейс PaymentClient
:
@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
является атрибут конфигурации
, поддерживающий OAuth2 для OpenFeign. После этого мы определяем класс 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
4.1. Зависимости
Чтобы добавить поддержку OAuth2 в Spring Cloud OpenFeign, нам нужно добавить spring-security-oauth2-client
и spring-boot-starter-security
в наш файл pom.xml
:
<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 и определим bean-компонент
requestInterceptor()
:
@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());
};
}
}
В bean- компоненте requestInterceptor()
мы используем классы ClientRegistration
и OAuthClientCredentialsFeignManager
для регистрации клиента oauth2 и получения токена доступа с сервера авторизации. Для этого нам нужно определить свойства клиента oauth2
в нашем файле application.properties :
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;
}
Мы используем классы OAuth2AuthorizeRequest
и OAuth2AuthorizedClient
для получения токена доступа с сервера авторизации. Теперь для каждого запроса перехватчик 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());
}
}
В этом тесте мы вызываем API getPayments()
. PaymentClient под капотом подключается к клиенту OAuth2
и получает токен доступа с помощью перехватчика.
6. Заключение
В этой статье мы настроим необходимую среду для вызова безопасного API. Затем мы настраиваем OpenFeign для вызова безопасного API на практическом примере. Для этого добавляем и настраиваем перехватчик в OpenFeign. Перехватчик управляет клиентом OAuth2 и добавляет токен доступа к запросу.
Как всегда, полный исходный код этого руководства доступен на GitHub . Кроме того, исходный код ресурса и сервера авторизации доступен на GitHub .