How to handle expiring refresh tokens in spring boot rest template client - java

I'm working on integrating a third party API in my spring boot application.
How the third party API authentication works:
After initial authorisation, I'm provided with refresh token and
access token that expires after a given time
After the access token expires I use the refresh token to get a new access token AND a new refresh token
With the current access token I can make calls to the API.
Is there a way to seamlessly handle such case using RestTemplate?
I've tried handling this case manually, so if I got 401 back from the API I sent a refresh token request, rewrote the keys I got back and retried the request, not really sure how to handle storing the api keys in case I need to restart the server.

This is easily done with a ClientHttpRequestInterceptor in which you can replace a requests header if e.g. a 401 occured:
#Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
if(response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
request.getHeaders().replace("Auth-Header", getNewToken());
return execution.execute(request, body);
}
return response;
}
See here for further guidance.

Related

Webclient - refresh API key on unauthorized

I'm looking for solution how to properly implement token refreshing mechanism.
A token should be changed after we got 401, or 403 http status code from external API. I'd like to call login endpoint to obtain a valid token, save the token into database and try again with the new token.
Currently, I'm having problem with reactor and streams. Below are two methods that contains core functionality.
register method (external call)
public Mono<Void> register(UserConfiguration configuration) {
return externalClient.post()
.uri("http://example.com")
.body(Mono.just(new SomeRequest()), SomeRequest.class)
.header(AUTHORIZATION_TOKEN_HEADER, token)
.exchangeToMono(clientResponse -> handleResponse(clientResponse, Void.class, configuration))
.retryWhen(Retry.max(5)
.doBeforeRetry(retrySignal -> log.info("Unauthorized request"))
.filter(UnauthorizedException.class::isInstance))
.then();
}
handleResponse method
private <R> Mono<R> handleResponse(ClientResponse clientResponse, Class<R> clazz, Configuration configuration, ) {
if (clientResponse.statusCode().equals(HttpStatus.OK)) {
return clientResponse.bodyToMono(clazz);
} else if (clientResponse.statusCode().equals(HttpStatus.FORBIDDEN) || clientResponse.statusCode().equals(HttpStatus.UNAUTHORIZED)) {
return authenticationService.refreshToken(configuration).flatMap(conf -> Mono.error(new UnauthorizedException()));
} else {
return clientResponse.createException()
.flatMap(Mono::error);
}
}
register is responsible for calling external API. handleResponse take care of handling response status, and when there is unauthorized response, the method invokes authenticationService in order to obtain a new token and save the token into db (UserConfiguration). Retry detects an exception instance and when there is problem with a token, it enforces the stream going again. Unfortunately, register method uses the same instance of UserConfiguration so values there are outdated as well as token is not valid anymore.
What would be the better approach? Now I can see the only workaround to start a stream from getting UserConfiguration, so on retry we will have the latest state of db. It seems to work, but it's not perfect from a performance view.
Might be late, but in case it helps someone else: you could use the onErrorResume method to filter for 401 and 403 HTTP responses and in those cases, renew the token, save it in the database and call the register method with the new user configuration.

What is the correct way of retrieving an OAuth2 Bearer token in Spring

I am making service to service requests using Spring's WebClient that require an OAuth2 bearer token to be added as a header to the request. I Can do this relatively easily by creating an ExchangeFilterFunction that intercepts the request, retrieves an access token, adds it to the header, and continues on. Since this is not a user request, the SecurityContextHolder does not contain an Authentication that would hold an access token for me, so instead of retrieving from that, I would like to get an access token based on my Spring security configuration (currently defined in the spring.security.oauth2.client.registration and provider properties).
The way I'm doing this now is by Autowiring an OAuth2ClientContext and then getting the AccessToken from it. Reducing the code only to what I care about for this question, I have:
#Component
public class OAuth2WebClientFilter implements ExchangeFilterFunction {
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
// simple retrieval of the token
String oAuth2Token = oAuth2ClientContext.getAccessToken().getValue();
// adding the token to the header of the request
request = ClientRequest.from(request).header(HttpHeaders.AUTHORIZATION, "Bearer " + oAuth2Token).build();
return next.exchange(request);
}
}
This does exactly what I want it to. However, I have recently upgraded spring-security-oauth2 to 2.5.0.RELEASE, and it is saying the OAuth2ClientContext is deprecated, but I haven't found a simple replacement for this process. So is there still a way to get an access token in a relatively simple fashion like above, and if so, how?
Also note: this concept is used elsewhere in the project and not just for the WebClient, so I'm looking to see how to properly replace an injected OAuth2ClientContext. Thanks!
Spring Security provides an exchange filter function called ServletOAuth2AuthorizedClientExchangeFilterFunction.
The ServletOAuth2AuthorizedClientExchangeFilterFunction provides a
simple mechanism for requesting protected resources by using an
OAuth2AuthorizedClient and including the associated OAuth2AccessToken
as a Bearer Token. It directly uses an OAuth2AuthorizedClientManager
and therefore inherits the following capabilities:
An OAuth2AccessToken will be requested if the client has not yet been
authorized.
authorization_code - triggers the Authorization Request redirect to
initiate the flow
client_credentials - the access token is obtained directly from the
Token Endpoint
password - the access token is obtained directly from the Token
Endpoint
If the OAuth2AccessToken is expired, it will be refreshed (or renewed)
if an OAuth2AuthorizedClientProvider is available to perform the
authorization
See https://docs.spring.io/spring-security/reference/servlet/oauth2/client/authorized-clients.html#oauth2Client-webclient-servlet for details.

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.)

Getting exception when requesting XSRF-TOKEN using Spring RestTemplate

I'm trying to call a service which has CSRF enabled and all it's endpoints are configured to request authentication header from the user.
I'm using Spring RestTemplate as follows:
ResponseEntity<String> responseEntity = getRestTemplate().exchange(
"localhost:9090/",
"HEAD",
entity,
String.class);
return responseEntity.getBody();
However, I'm not able to read the Headers from the response as I'm getting HTTP 401 error.
My workaround is to read the token from the exception that RestTemplate throws HttpClientErrorException. Like this:
exception.getResponseHeaders().get("Set-Cookie");
for (String header : headers) {
if (header.startsWith("XSRF-TOKEN")) {
token = header.split("=")[1];
break;
}
}
Is there any way to get XSRF-TOKEN token with out relying on reading it from the exception?
You are not getting an exception when accessing with GET method. Hence, I would create a get endpoint for retrieving the token and then use it for next POST calls.
Hope that approach makes sense.
the csrf only blocks requests of type post, put, delete ... that is, the get is free, therefore in order to obtain the token, first you have to make a request to a get method and extract the token from there that you would use to the next requests.
in case the token is not generated, add this to the configure of your security configuration:
http.csrf (). csrfTokenRepository (CookieCrsfTokenRepository.withHttpOnlyFalse) .any () ........
XSRF-TOKEN following spring specification is marker for header by default. So you should try get it in this way:
List tokenList = responseEntity.getHeaders().get("XSRF-TOKEN");
This collection consist of single element as usual, so first element should be your token.

Cloud Endpoints: Access Paramters in Servlet Filter

I'm trying to build an api with Google Cloud Endpoints.
As Cloud Endpoints does not provide authentication beside Googles own OAuth I try to build my own. Therefore I want to access the parameters provided for the API (for example #Named("token") token) inside a servlet filter.
Unfortunately I cannot find any of the provided information inside the httpRequest. Is that normal? Is there a possibility to access the parameters?
I would appreciate if someone could help me!
UPDATE:
With the infos from jirungaray I tried to build an authentication using headers but ran into the same problem. Used a REST-Client to send some headers as I could not figure out how to do this with the API Explorer. Inside my filter I try to access the token from the headers:
#Override
public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader(Constants.AUTH_TOKEN);
...
chain.doFilter(request, response);
}
The reason why I try to do something like this, is that I'm using Guice for Dependency Injection and want my token to be injected inside another object.
With Guice I have the following Provider using the token to inject a FacebookClient (using the token) per request.
#Provides
public FacebookClient getFacebookClientProvider(#Named("fbToken") Provider<String> fbToken) {
return new DefaultFacebookClient(fbToken.get(), Version.VERSION_2_2);
}
As described in the Guice wiki (SevletModule) this uses a sevlet filter to get the information from the request.
Is there any solution to achieve this kind of DI with Cloud Endpoints?
Philip,
Yes, it does makes sense you are getting an empty request. Your endpoint calls are first handled by Google (they receive the API calls) and then those are processed and sent to a handler in your app. As this is all done in the background it's very easy to miss that your endpoints aren't actually getting the same request you sent, they get a completely different request sent from Google's infrastructure.
Even though your approach should work including tokens info in url makes them easier to sniff, even if you use SSL or encrypt your params the token is there in plain sight.
For what you are trying to achieve I recommend you include the token as a header in your request and retrieve that header by accessing the HTTPRequest directly on the endpoint, this is injected automatically if you include an HTTPServletRequest param in you endpoint method.
eg.
public APIResponse doSomething(SomeComplexRquestModel request,
HttpServletRequest rawRequest) {
}
If you still feel you should go with your original approach just comment and I'll help you debug the issue.

Categories

Resources