Open mcordeiro73 opened 3 months ago
@mcordeiro73, can you please share an example of how you're setting up the RestClient
?
@rwinch, do you think this makes sense for imperative non-web apps as #14350 did for reactive non-web apps?
Below is the configuration of my RestClient
. I've also added some custom OAuth classes I'm setting up to handle the OAuth token requests and removal of authorized client on token errors.
@Bean
public RestClient oktaRestClient(RestClient.Builder builder, ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService oauth2AuthorizedClientService) {
OAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oauth2AuthorizedClientService);
ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("okta");
return builder.requestInterceptor(new Oauth2ClientHttpRequestInterceptor(authorizedClientManager, clientRegistration))
.defaultStatusHandler(RemoveAuthorizedClientOAuth2ResponseErrorHandler.unauthorizedStatusPredicate(), RemoveAuthorizedClientOAuth2ResponseErrorHandler.createErrorHandler(oauth2AuthorizedClientService, clientRegistration))
.build();
}
public class Oauth2ClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
private final OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager;
private final ClientRegistration clientRegistration;
public Oauth2ClientHttpRequestInterceptor(@NotNull OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager, @NotNull ClientRegistration clientRegistration) {
this.oAuth2AuthorizedClientManager = oAuth2AuthorizedClientManager;
this.clientRegistration = clientRegistration;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
request.getHeaders()
.setBearerAuth(getBearerToken());
return execution.execute(request, body);
}
private String getBearerToken() {
OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(clientRegistration.getClientId())
.build();
OAuth2AuthorizedClient client = oAuth2AuthorizedClientManager.authorize(oAuth2AuthorizeRequest);
Assert.notNull(client, () -> "Authorized client failed for Registration id: '" + clientRegistration.getRegistrationId() + "', returned client is null");
return client.getAccessToken()
.getTokenValue();
}
}
public class RemoveAuthorizedClientOAuth2ResponseErrorHandler extends DefaultResponseErrorHandler {
private static final Predicate<HttpStatusCode> STATUS_PREDICATE = httpStatusCode -> httpStatusCode.value() == HttpStatus.UNAUTHORIZED.value();
private final OAuth2AuthorizedClientService oauth2AuthorizedClientService;
private final ClientRegistration clientRegistration;
public RemoveAuthorizedClientOAuth2ResponseErrorHandler(@NotNull OAuth2AuthorizedClientService oauth2AuthorizedClientService, @NotNull ClientRegistration clientRegistration) {
this.oauth2AuthorizedClientService = oauth2AuthorizedClientService;
this.clientRegistration = clientRegistration;
}
@Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
if (STATUS_PREDICATE.test(clientHttpResponse.getStatusCode())) {
oauth2AuthorizedClientService.removeAuthorizedClient(clientRegistration.getRegistrationId(), clientRegistration.getClientId());
}
super.handleError(clientHttpResponse);
}
public static RestClient.ResponseSpec.ErrorHandler createErrorHandler(@NotNull OAuth2AuthorizedClientService oauth2AuthorizedClientService, @NotNull ClientRegistration clientRegistration) {
ResponseErrorHandler responseErrorHandler = new RemoveAuthorizedClientOAuth2ResponseErrorHandler(oauth2AuthorizedClientService, clientRegistration);
return (request, response) -> responseErrorHandler.handleError(response);
}
public static Predicate<HttpStatusCode> unauthorizedStatusPredicate() {
return STATUS_PREDICATE;
}
}
@wilkinsona This sounds like a useful change since clients are not necessarily web applications.
Thanks, @rwinch.
When we come to implement this, we may want to review the package that OAuth2ClientAutoConfiguration
is in. It's currently in org.springframework.boot.autoconfigure.security.oauth2.client.servlet
which arguably doesn't make sense if it's going to apply to everything other than reactive web applications.
Attempting to setup a Spring Boot application that is not a Web Application, unable to rely on auto configuration of OAuth2Client due to
OAuth2ClientAutoConfiguration
having@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
.The batch job we are attempting to setup has a client to a Rest API that requires OAuth2 authentication. When attempting to Autowire
ClientRegistrationRepository
andOAuth2AuthorizedClientService
into a Bean method in order so setup theRestClient
, received error thatParameter 1 of method oauth2RestClient in com.sample.batch.configuration.RestClientConfiguration required a bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' that could not be found
.We were able to get around this by providing our own
ClientRegistrationRepository
andOAuth2AuthorizedClientService
beans, but I believe that these auto configured beans should not require the Spring Boot app to be running in a servlet environment.