У меня есть веб-приложение Spring, в котором настроен сервер ресурсов oauth2 для конечных точек API и совершенно другой клиент oauth2 для вызовов REST, которые он выполняет. Клиент oauth2 должен иметь тип предоставления пароля. Имя пользователя и пароль фиксированные (не поступают из HTTP-запроса). Моя проблема в том, что через 30 минут как токен доступа, так и токен refre sh истекают, поэтому нет возможности сделать refre sh. Я ожидал, что Spring Security просто запросит новый токен доступа, но нет. Он вызывает конечную точку REST с истекшим сроком действия и возвращает 403. Вот что у меня есть:
application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://our.idp.keycloak.host/auth/realms/firstrealm
client:
registration:
my-client-authorization:
client-id: my_client
client-secret: ${CLIENT_SECRET}
authorization-grant-type: password
scope: openid, profile
provider:
my-client-authorization:
token-uri: https://our.idp.keycloak.host/auth/realms/secondrealm/protocol/openid-connect/token
MyClientConfig. java:
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.Map;
@Configuration
@RequiredArgsConstructor
public class MyClientConfig {
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("my-client-authorization");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.baseUrl("https://the.api.host.to.call")
.build();
}
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository
) {
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.password()
.build();
DefaultOAuth2AuthorizedClientManager result = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository,
authorizedClientRepository
);
result.setAuthorizedClientProvider(authorizedClientProvider);
result.setContextAttributesMapper(oAuth2AuthorizeRequest -> Map.of(
OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "user",
OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password"
));
return result;
}
}
Сам вызов API:
private <T> T callApi(Function<UriBuilder, URI> uriFunction, Class<T> resultType) {
return this.webClient
.get()
.uri(uriFunction)
.retrieve()
.bodyToMono(resultType)
.block();
}
Он работает при первом вызове. Но через 30 минут токен сдох, и я понятия не имею, как получить новый. Если я переключу это на тип предоставления client_credentials, он работает, он автоматически получает новый токен, когда это необходимо. Но по какой-то причине я не могу сделать то же самое для типа предоставления пароля.
Изменить: Так что мне удалось исправить это благодаря этому: https://github.com/spring-projects/spring-security/issues/8831. Когда я настраиваю WebClient также для refreshToken, он вылетает, когда истекает срок действия токена refre sh. Но когда вы снова выполните запрос после этого, он получит новый токен. Поэтому мне пришлось обернуть вызов API в try catch, и если это ошибка, которая мне небезразлична, я снова вызываю API. Это не очень элегантное решение, но оно работает.