Spring WebClient with Custom OAuth2 request - java

I'm building a app that has to communicate with a REST service that is secured using OAuth2 with grant type client_credentials, the catch is that the /oauth/token endpoint is expecting a custom header, for simplification, let's call it "Custom-Header".
My problem is that there is no example or trace in the documentation in how to accomplish this.
My code is as follows
ClientRegistration client = ClientRegistration
.withRegistrationId(authUser)
.tokenUri(authUrl)
.clientId(authUser)
.clientSecret(authPassword)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope(authScope)
.build();
ReactiveClientRegistrationRepository clientRegistrations =
new InMemoryReactiveClientRegistrationRepository(client);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauthFilter =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauthFilter.setDefaultClientRegistrationId(authUser);
this.webClient = WebClient.builder()
.filter(oauthFilter)
.defaultHeaders(httpHeaders -> {
httpHeaders.add(CUSTOM_HEADER, customHeader);;
})
.build();
As you can see, I'm setting the custome header in the WebClient, but it doesn't reach the oauth filter.
Any help will be appreciated since I've been going back and forth for two days now.

Finally my solution was to reimplment the OAuth2ExchangeFilterFunction and implement my own CustomWebClientCredentialsTokenResponseClient
In CustomWebClientCredentialsTokenResponseClient I added the custom headers that the provider is expecting and in the method createDefaultAuthorizedClientManager() I had to create an instance of the CustomWebClientCredentialsTokenResponseClient I created.

Related

RestTemplate seems to keep Authorization header

I'm using Spring 4 RestTemplate to do some server-side API calls.
The RestTemplate instance is a custom one (not Spring Boot default) using Apache HttpClient created as follows:
PoolingHttpClientConnectionManager cm;
...
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
Some of the API calls use HTTP Basic Authentication and thus need to have an Authorization header. I'm adding this on a RequestEntity and then performing an exchange() call on the RestTemplate and this works fine.
RequestEntity<Void> requestEntity;
requestEntity = RequestEntity.get(uri)
.accept(MediaType.valueOf("application/repository.folder+json"))
.acceptCharset(UTF_8)
.header("Accept-Encoding", "")
.header("Authorization", apiBasicAuthHeader())
.build();
Some other API calls (to the same backend server) should not use HTTP Basic Authentication and instead use a pre-authenticated token supplied as a request parameter.
RequestEntity<Void> requestEntity = RequestEntity.get(uriWithToken)
.accept(APPLICATION_JSON)
.acceptCharset(UTF_8)
.header("Accept-Encoding", "")
.build();
operations.exchange(requestEntity, ResourceLookupResults.class)
This also works fine by itself.
However, if I do an API call using the Authorization header first and then try to do one with the pre-authenticated token (with the same RestTemplate), it seems that the Authorization header is still sent on the 2nd request. I'd expect the header added to the RequestEntity to be added only for that specific request and not for subsequent requests that don't need it. Why is this and what is the best way to avoid this (like using separate RestTemplate instances)?

How to deserialize a ClientResponse body in Spring test environment?

I'm trying to create a ClientResponse in test and use it for testing a service, which also does deserialization with standard way response.bodyToMono(..class..). But it appears that there is something wrong in the way I build a fake client response. Because I receive UnsupportedMediaTypeException in tests.
Nevertheless the same code work fine in runtime SpringBoot app, when WebClient returns ClientResponse (which is built internally).
Let's see at the simplest case hich fails with
org.springframework.web.reactive.function.UnsupportedMediaTypeException:
Content type 'application/json' not supported for bodyType=java.lang.String[]
void test()
{
String body = "[\"a\", \"b\"]";
ClientResponse response = ClientResponse.create(HttpStatus.OK)
.header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE)
.body(body)
.build();
String[] array = response.bodyToMono(String[].class).block();
assertEquals(2, array.length);
}
Please, help me to undeerstand, how the client response should be build to allow a standard (json -> object) deserialization in test environment.
A ClientResponse created manually does not have access to Jackson2Json exchange strategies in default list. Probably it could be configured with Spring auto-configuration, which is turned off in tests without Spring context.
Here is the straightforward way to force (de)serialization String <-> json:
static ExchangeStrategies jacksonStrategies()
{
return ExchangeStrategies
.builder()
.codecs(clientDefaultCodecsConfigurer ->
{
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
}).build();
}
Then use it in the create function
ClientResponse.create(HttpStatus.OK, jacksonStrategies())...

How to have JWT authentication between micro services in kubernetes cluster

We have 8 java microservices talking to each other in kubeneters cluster. Each microservice is bundled with auth library which intercepts and validates/renews JWT token for each REST request to controllers.
Scenario:
From Frontend, we get access token for the first time, Authentication gets successful. Lets say
Frontend hit 'Microservice A' with access token - Successful
'Microservice A' internally hits 'Microservice B' via restTemplate.
My 'Microservice B' also needs logged in user details.
Issue: I have to pass same access token from 'A' to 'B' but I am not able to get access token in Controller/Service logic but can get only in filters where token is being validated. I can get token in Rest Controllers by adding following argument in all rest methods in controller:
#RequestHeader (name="Authorization") String token
But I dont want to go with this approach as I have to pass this token to everywhere till end and have to declare this argument in all APIS.
I want to get token from TokenStore by passing authentication object. We are using Oauth2 and I checked the code in library, There are many tokenStore providers.
In DefaultTokenServices.java class, I am calling
Authentication auth = SecurityContextHolder.getContext().getAuthentication() // Passed this auth to tokenStore
String token = tokenStore.getAccessToken(auth).getValue(); // NullPointerException
My code is going through JWTTokenStore provider which is returning null. I checked, there is a provider called InMemoryTokenStore.class which actually extrActs token from store. But my flow is not going into in memory implementation.
Is there any way I can get token afterwards without grabbing it in controller via arguments? or how can I enable/use inMemoryTokenStore?
Also recommend something better for kubernetes intercommunication authentication?
TIA
It looks like you're using Spring (and Spring Security), so I believe the relevant part of the docs is the part on Bearer Token Propagation.
Its recommendation is to use a WebClient (the recommended replacement for RestTemplate as of Spring 5) that uses the provided ServletBearerExchangeFilterFunction to automagically propagate the JWT token from the incoming request into the outgoing request:
#Bean
public WebClient rest() {
return WebClient.builder()
.filter(new ServletBearerExchangeFilterFunction())
.build();
}
On RestTemplate, the docs say:
"There is no dedicated support for RestTemplate at the moment, but you can achieve propagation quite simply with your own interceptor"
and the following example is provided:
#Bean
RestTemplate rest() {
RestTemplate rest = new RestTemplate();
rest.getInterceptors().add((request, body, execution) -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return execution.execute(request, body);
}
if (!(authentication.getCredentials() instanceof AbstractOAuth2Token)) {
return execution.execute(request, body);
}
AbstractOAuth2Token token = (AbstractOAuth2Token) authentication.getCredentials();
request.getHeaders().setBearerAuth(token.getTokenValue());
return execution.execute(request, body);
});
return rest;
}
I don't believe you need to be looking at TokenStores if all you're trying to do is propagate the token. Remember everything relevant about a JWT should be inside the token itself. (Which is why the doc for the JwtTokenStore explains that it doesn't actually store anything, but just pulls info out of the token, and will return null for some methods, including the getAccessToken() method you're calling.)

Angular 6 and spring boot remember me with session based login

Currently, In our spring boot application, we are authenticating users based on HeaderHttpSessionStrategy which is session based authentication. Trying to add remember me functionality, but spring is not storing users into persistent_logins either cookie also not created client side.
I have added
//this line added to HttpSecurity object
.and().rememberMe().rememberMeParameter("rememberMe")
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(86400)
#Bean
public DataSource getDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("${db.driver}");
dataSourceBuilder.url("${db.url}");
dataSourceBuilder.username("${db.username}");
dataSourceBuilder.password("${db.password}");
return dataSourceBuilder.build();
}
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(getDataSource());
return jdbcTokenRepository;
}
We are using Angular 6 as Front end client, So sending rememberMe header in Post method of an authentication endpoint.
Please suggest someone what I am missing here.
Thanks in advance
After so much digging into remember-me authentication, I am able to achieve the functionality.
credit must go to the author of this link
This link alone is enough for the backend part.
When it comes to angular 6, read the response as ResponseEntity so it will automatically create a cookie for you. (If in case you have data to receive from auth endpoint then add them to response header, read them in component as data.headers.get('some_header_from_api'))
getToken(username:string,password:string,rememberMe:string){
let httpHeaders = new HttpHeaders({
'Cache-Control': 'no-cache',
'X-Requested-With': 'XMLHttpRequest',
'Authorization': 'Basic '+ btoa(username +':'+ password),
});
return this.http.get<HttpResponse<Object>>(this.BASE_URL+"?rememberMeChecked="+rememberMe,{ headers: httpHeaders,withCredentials: true,observe: 'response'});
}
Thats it. Wait if you are thinking of how to send a remember-me cookie in each request, actually, you don't need to do any additional work, angular automatically adds cookies for you.

Using Spring Boot 2 WebClient, in threadsafe / per-request manner, how send diff headers every request?

In Spring Boot 1.5.x, I could use interceptors with AsyncRestTemplate to grab headers from an incoming request to a RestController endpoint and put them in any exchange requests made via the AsyncRestTemplate.
I don't see how this can work with the WebClient. It looks like if you build a WebClient that all its headers, etc are set and unchangeable:
WebClient client = WebClient.builder()
.baseUrl( "http://blah.com" )
.defaultHeader( "Authorization", "Bearer ey..." )
.build();
While I can change these using client.mutate(), that instantiates a completely new WebClient object. I'd prefer not to have to create a new one on every request. Is there no way to keep a WebClient and have per-request headers and other parameters?
It seems like a big waste and poor performance to force creating a new object every time.
What you're using here are the default headers that should be sent for all requests sent by this WebClient instance. So this is useful for general purpose headers.
You can of course change the request headers on a per-request basis like this:
Mono<String> result = this.webClient.get()
.uri("/greeting")
.header("Something", "value")
.retrieve().bodyToMono(String.class);
If you wish to have an interceptor-like mechanism to mutate the request before sending it, you can configure the WebClient instance with a filter:
WebClient
.builder()
.filter((request, next) -> {
// you can mutate the request before sending it
ClientRequest newRequest = ClientRequest.from(request)
.header("Something", "value").build();
return next.exchange(newRequest);
})
Please check out the Spring Framework documentation about WebClient.

Categories

Resources