OAuth2RestOperations uses token obtained from request header instead of requesting the auth server - spring

I have a spring boot client app which uses OAuth2RestTemplate as OAuth2Client. I have configured the OAuth2RestTemplate to call authserver and add token obtained from it to header for accessing resource server.
The problem occured is that whenever i call the method in client app to access resource server using restTemplate, it used the token coming from header of the request of the client app instead of calling the auth server.
It uses that token and the token gets rejected by my resource server. And after it is rejected, it then only calls the auth server and puts correct token and again sends the request to my resource server.
Is there any way to make rest template not use the token from the header and call the auth server for the token before connecting resource server?
Thank u
My config class
#Configuration
#EnableOAuth2Client
public class OAuth2ClientConfig {
#Autowired
ConfigProperties configProperties;
#Bean("oauth2AuthServer")
public OAuth2RestOperations restTemplate(OAuth2ClientContext oauth2ClientContext) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource(), oauth2ClientContext);
oAuth2RestTemplate.setAccessTokenProvider(new CustomResourceOwnerPasswordAccessTokenProvider());
return oAuth2RestTemplate;
}
#Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setId(configProperties.getClientId());
resource.setAccessTokenUri(configProperties.getAccessTokenUri());
resource.setClientId(configProperties.getClientId());
resource.setClientSecret(configProperties.getClientSecret());
resource.setGrantType(configProperties.getGrantType());
resource.setClientAuthenticationScheme(AuthenticationScheme.header);
resource.setAuthenticationScheme(AuthenticationScheme.header); //
resource.setUsername(configProperties.getUsername());
resource.setPassword(configProperties.getPassword());
return resource;
}
}
My serviceImpl method is
#Autowired
#Qualifier("oauth2AuthServer")
private OAuth2RestOperations oauth2RestOperations;
RequestResponse callResourceServer(ResourceRequest request) {
try {
RequestResponse response;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ReseourceRequest> entity = new HttpEntity<>(request, headers);
response = this.oauth2RestOperations.postForObject(
microServiceConfig.getUrl(),
entity,
RequestResponse.class
);
return response;
} catch (Exception ex) {
log.error(ex);
throw new exception("error");
}
}

I see BaseOAuth2ProtectedResourceDetails and Oauth2RestTemplate deprecated, Can we still use them ? or we should migrate to 5.x options

Related

How to cache a JWT in Spring-Security from an OAuth2Client

In my case I have an application for SpringBootAdmin. SpringBootAdmin sends requests to a lot of applications all the time.
For these requests I set an access token (JWT) which I pull from Keycloak via the AuthorizedClientServiceOAuth2AuthorizedClientManager.
Now the problem is that this token is not cached, and Spring-Security sends about 100 requests per minute to Keycloak to get the access token.
So is there a way to cache this JWT ?
Here is my SecurityConfig:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
public void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.oauth2Client()
.and()
.csrf()
.ignoringAntMatchers("/**");
}
}
My ClientRegistration:
#Configuration
public class ClientRegistrationConfiguration
{
private static final String KEYCLOAK = "keycloak";
#Bean
public ClientRegistration clientRegistration(OAuth2ClientProperties properties)
{
return withRegistrationId(KEYCLOAK)
.tokenUri(properties.getProvider().get(KEYCLOAK).getTokenUri())
.clientId(properties.getRegistration().get(KEYCLOAK).getClientId())
.clientSecret(properties.getRegistration().get(KEYCLOAK).getClientSecret())
.authorizationGrantType(CLIENT_CREDENTIALS)
.build();
}
#Bean
public ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration)
{
return new InMemoryClientRegistrationRepository(clientRegistration);
}
#Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository)
{
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
#Bean
public AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientServiceOAuth2AuthorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService)
{
var authorizedClientProvider = builder().clientCredentials().build();
var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
And my RequestConfiguration:
#Configuration
public class HttpRequestConfiguration
{
#Bean
public HttpHeadersProvider customHttpHeadersProvider(AuthorizedClientServiceOAuth2AuthorizedClientManager clientManager)
{
return instance ->
{
var httpHeaders = new HttpHeaders();
var token = Objects.requireNonNull(clientManager.authorize(withClientRegistrationId("keycloak").principal("Keycloak").build())).getAccessToken();
httpHeaders.add("Authorization", "Bearer " + token.getTokenValue());
return httpHeaders;
};
}
}
Your headers provider is a bean, so you can simply cache the token there. If you write your provider like that:
#Bean
public HttpHeadersProvider customHttpHeadersProvider(AuthorizedClientServiceOAuth2AuthorizedClientManager clientManager)
{
var token = Objects.requireNonNull(clientManager.authorize(withClientRegistrationId("keycloak").principal("Keycloak").build())).getAccessToken();
return instance ->
{
var httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization", "Bearer " + token.getTokenValue());
return httpHeaders;
};
}
Then the token will be requested once, only when the bean is created. This will fix calling Keycloak on each request but has the usual problem of caching - at some point the access token will expire and you need a way to get a new token. One way to do it would be to catch 401 errors from your client and force recreation of the customHttpHeadersProvider bean when that happens.
Another way would be to create an object which will be a "token provider" and a bean itself. That object would keep the token in memory and have a method to refresh the token. Then you could create the authorization header near the request itself, instead of using a headers provider bean. You will have more control then over when do you want to refresh the access token.

Log OAuth calls made by the webclient library

I have an application that uses microservice architecture and services are protected by Spring OAuth2 Client Credentials Grant type. We use spring WebClient to call from one service to another along with Spring OAuth client library as it handles fetching OAuth credentials transparently.
As we know the Spring (OAuth client) makes a call to Authorization server to request a new token from Auth server when it doesn't have a token or the current token is expired. How can we log a statement on client side whenever Spring (OAuth client) makes a call to the OAuth server? We would like to log this statement to see how frequently the OAuth client library is making calls to the Authorization Server.
public WebClient webClient()
{
return WebClient.builder()
.apply(filter())
.baseUrl("http://localhost:8082/resource")
.build();
}
public Consumer<Builder> filter()
{
ServletOAuth2AuthorizedClientExchangeFilterFunction oAuth =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oAuth.setDefaultClientRegistrationId("my-client");
return oAuth.oauth2Configuration();
}
Define a custom token client that logs when sending a token request to the authorization server.
#Slf4j
public class LoggingClientCredentialsTokenResponseClient
implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
private DefaultClientCredentialsTokenResponseClient delegate =
new DefaultClientCredentialsTokenResponseClient();
#Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
log.debug("Sending request {}", clientCredentialsGrantRequest);
var response = delegate.getTokenResponse(clientCredentialsGrantRequest);
log.debug("Received response {}", response);
return response;
}
}
Create an authorized client manager that uses the custom token client.
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService) {
var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials(builder ->
builder.accessTokenResponseClient(
new LoggingClientCredentialsTokenResponseClient()))
.build();
var authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
Create a WebClient that uses the authorized client manager.
#Bean
public WebClient oauth2WebClient(
WebClient.Builder webClientBuilder,
OAuth2AuthorizedClientManager authorizedClientManager) {
var oauth2Filter =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Filter.setDefaultClientRegistrationId("my-client");
return webClientBuilder.apply(oauth2Filter.oauth2Configuration())
.baseUrl("http://localhost:8082/resource")
.build();
}

Create route in Spring Cloud Gateway with OAuth2 Resource Owner Password grant type

How to configure a route in Spring Cloud Gateway to use an OAuth2 client with authorization-grant-type: password? In other words, how to add the Authorization header with the token in the requests to an API? Because I'm integrating with a legacy application, I must use the grant type password.
I have this application:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("route_path", r -> r.path("/**")
.filters(f -> f.addRequestHeader("Authorization", "bearer <token>"))
.uri("http://localhost:8092/messages"))
.build();
}
}
Replacing the <token> with an actual token, everything just works fine.
I found this project that does something similar: https://github.com/jgrandja/spring-security-oauth-5-2-migrate. It has a client (messaging-client-password) that is used to configure the WebClient to add OAuth2 support to make requests (i.e. by adding the Authorization header).
We can't use this sample project right away because Spring Cloud Gateway is reactive and the way we configure things changes significantly. I think to solve this problem is mostly about converting the WebClientConfig class.
UPDATE
I kinda make it work, but it is in very bad shape.
First, I found how to convert WebClientConfig to be reactive:
#Configuration
public class WebClientConfig {
#Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth.setDefaultOAuth2AuthorizedClient(true);
oauth.setDefaultClientRegistrationId("messaging-client-password");
return WebClient.builder()
.filter(oauth)
.build();
}
#Bean
ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.refreshToken()
.password()
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// For the `password` grant, the `username` and `password` are supplied via request parameters,
// so map it to `OAuth2AuthorizationContext.getAttributes()`.
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
return authorizedClientManager;
}
private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
return authorizeRequest -> {
Map<String, Object> contextAttributes = Collections.emptyMap();
ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
String username = serverWebExchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
String password = serverWebExchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = new HashMap<>();
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return Mono.just(contextAttributes);
};
}
}
With this configuration, we can use the WebClient to make a request. This somehow initializes the OAuth2 client after calling the endpoint:
#GetMapping("/explicit")
public Mono<String[]> explicit() {
return this.webClient
.get()
.uri("http://localhost:8092/messages")
.attributes(clientRegistrationId("messaging-client-password"))
.retrieve()
.bodyToMono(String[].class);
}
Then, by calling this one we are able to get the reference to the authorized client:
private OAuth2AuthorizedClient authorizedClient;
#GetMapping("/token")
public String token(#RegisteredOAuth2AuthorizedClient("messaging-client-password") OAuth2AuthorizedClient authorizedClient) {
this.authorizedClient = authorizedClient;
return authorizedClient.getAccessToken().getTokenValue();
}
And finally, by configuring a global filter, we can modify the request to include the Authorization header:
#Bean
public GlobalFilter customGlobalFilter() {
return (exchange, chain) -> {
//adds header to proxied request
exchange.getRequest().mutate().header("Authorization", authorizedClient.getAccessToken().getTokenType().getValue() + " " + authorizedClient.getAccessToken().getTokenValue()).build();
return chain.filter(exchange);
};
}
After running this three requests in order, we can use the password grant with Spring Cloud Gateway.
Of course, this process is very messy. What still needs to be done:
Get the reference for the authorized client inside the filter
Initialize the authorized client with the credentials using contextAttributesMapper
Write all of this in a filter, not in a global filter. TokenRelayGatewayFilterFactory implementation can provide a good help to do this.
I implemented authorization-grant-type: password using WebClientHttpRoutingFilter.
By default, spring cloud gateway use Netty Routing Filter but there is an alternative that not requires Netty (https://cloud.spring.io/spring-cloud-gateway/reference/html/#the-netty-routing-filter)
WebClientHttpRoutingFilter uses WebClient for route the requests.
The WebClient can be configured with a ReactiveOAuth2AuthorizedClientManager through of an ExchangeFilterFunction (https://docs.spring.io/spring-security/site/docs/current/reference/html5/#webclient). The ReactiveOAuth2AuthorizedClientManager will be responsible of management the access/refresh tokens and will do all the hard work for you
Here you can review this implementation. In addition, I implemented the client-credentials grant with this approach

Unable to Refresh Token using Spring ClientCredentialsResourceDetails

I am using ClientCredentialsResourceDetails to setup my OAuth2RestTemplate object.
My OAuth2 enabled api is sending me back refresh_token in the response, which can be used to refresh access tokens.
However, Spring does not implement token refresh for ClientCredentialsResourceDetails object as it can be seen here
Is there a way to get token refresh working or am I doing anything incorrect ?
FYI, this is my piece of spring code for oauth beans
#Bean
#Primary
public OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails() {
final ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setClientId(clientKey);
resourceDetails.setClientSecret(clientSecret);
final URI accessTokenUri = UriComponentsBuilder.newInstance()
.host(host)
.path(tokenUrlPath)
.scheme(scheme)
.build()
.toUri();
resourceDetails.setAccessTokenUri(accessTokenUri.toString());
resourceDetails.setClientAuthenticationScheme(AuthenticationScheme.header);
return resourceDetails;
}
#Bean
public OAuth2RestTemplate oAuth2RestTemplate(
OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails
) {
final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(oAuth2ProtectedResourceDetails);
oAuth2RestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
return oAuth2RestTemplate;
}
It's because "4.4.3. Access Token Response" in RFC 6749 (The OAuth 2.0 Authorization Framework) says as follows:
If the access token request is valid and authorized, the authorization server issues an access token as described in Section 5.1. A refresh token SHOULD NOT be included. If the request failed client authentication or is invalid, the authorization server returns an error response as described in Section 5.2.
In short, Client Credentials Flow should not issue a refresh token. Spring complies with the requirement.

Can we override CheckTokenEndpoint and provide custom CheckTokenEndpoint in Spring Oauth2?

My application has separate authorization server and resource server. Authorization server provides access token to resource server. Resource server then sends the request for protected resource with access token.
Resource server uses RemoteTokenServices to validate whether the access token is proper or not.
#Bean
public RemoteTokenServices remoteTokenServices(final #Value("${auth.server.url}") String checkTokenUrl,
final #Value("${auth.server.clientId}") String clientId,
final #Value("${auth.server.clientsecret}") String clientSecret)
{
final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl+"?name=value");
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
return remoteTokenServices;
}
application.yml
auth:
server:
url: http://localhost:9191/api/oauth/check_token/
clientId: clientid
clientsecret: secret
I want to pass additional parameter like resource id so that I can verify if the token is authorized for that resource or not.
I want to get that parameter in org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint,
and want override below method to add some logic. Is it possible?
#RequestMapping(value = "/oauth/check_token")
#ResponseBody
public Map<String, ?> checkToken(#RequestParam("token") String value) {
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
}
if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
}
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, ?> response = accessTokenConverter.convertAccessToken(token, authentication);
return response;
}
How to send some parameter to oauth/check_token and override checkToken() method?
Basically what I am doing is when access token is generated, I am saving some record about the resources that token is allowed for.
When I receive the request for the resource on resource server, I want to pass the resource id to auth server and want to verify the token is authorized for that resource or not?
I've just create new CustomCheckTokenEndpoint and copy whole code of CheckTokenEndpoint then override checkToken(...) method
#Configuration
#EnableAuthorizationServer
public class CustomAuthorizationServer extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.pathMapping("/oauth/check_token", "/my/oauth/check_token");
}
}
**CustomCheckTokenEndpoint.java**
public class CustomCheckTokenEndpoint {
// copy whole CheckTokenEndpoint
#RequestMapping(value = "/my/oauth/check_token")
#ResponseBody
public Map<String, ?> checkToken(String value) {
// your code will be here
}
}

Resources