Reputation: 783
I have a microservice architecture, both of them securized by spring security an JWT tokens.
So, when I call my first microservice, I want to take the JWT token and send a request to another service using those credentials.
How can I retrieve the token and sent again to the other service?
Upvotes: 18
Views: 29743
Reputation: 121
May be a little bit late but I think this is a common question, regarding
Spring Security 6.0.0 for web client there is a class called ServletBearerExchangeFilterFunction
that you can use to read the token from the security context and inject it.
@Bean
public WebClient rest() {
return WebClient.builder()
.filter(new ServletBearerExchangeFilterFunction())
.build();
For RestTemplate
there is no automatic way and is recommended use a filter
@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;
}
Upvotes: 1
Reputation: 90
I think it is better to add the interceptor specifically to the RestTemplate, like this:
class RestTemplateHeaderModifierInterceptor(private val authenticationService: IAuthenticationService) : ClientHttpRequestInterceptor {
override fun intercept(request: org.springframework.http.HttpRequest, body: ByteArray, execution: ClientHttpRequestExecution): ClientHttpResponse {
if (!request.headers.containsKey("Authorization")) {
// don't overwrite, just add if not there.
val jwt = authenticationService.getCurrentUser()!!.jwt
request.headers.add("Authorization", "Bearer $jwt")
}
val response = execution.execute(request, body)
return response
}
}
And add it to the RestTemplate like so:
@Bean
fun restTemplate(): RestTemplate {
val restTemplate = RestTemplate()
restTemplate.interceptors.add(RestTemplateHeaderModifierInterceptor(authenticationService)) // add interceptor to send JWT along with requests.
return restTemplate
}
That way, every time you need a RestTemplate you can just use autowiring to get it. You do need to implement the AuthenticationService still to get the token from the TokenStore, like this:
val details = SecurityContextHolder.getContext().authentication.details
if (details is OAuth2AuthenticationDetails) {
val token = tokenStore.readAccessToken(details.tokenValue)
return token.value
}
Upvotes: 3
Reputation: 783
I've accomplished the task, creating a custom Filter
public class RequestFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader(RequestContext.REQUEST_HEADER_NAME);
if (token == null || "".equals(token)) {
throw new IllegalArgumentException("Can't retrieve JWT Token");
}
RequestContext.getContext().setToken(token);
chain.doFilter(request, response);
}
@Override
public void destroy() { }
@Override
public void init(FilterConfig arg0) throws ServletException {}
}
Then, setting in my config
@Bean
public FilterRegistrationBean getPeticionFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new RequestFilter());
registration.addUrlPatterns("/*");
registration.setName("requestFilter");
return registration;
}
With that in mind, I've create another class with a ThreadLocal variable to pass the JWT token from the Controller to the Rest Templace interceptor
public class RequestContext {
public static final String REQUEST_HEADER_NAME = "Authorization";
private static final ThreadLocal<RequestContext> CONTEXT = new ThreadLocal<>();
private String token;
public static RequestContext getContext() {
RequestContext result = CONTEXT.get();
if (result == null) {
result = new RequestContext();
CONTEXT.set(result);
}
return result;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor{
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
String token = RequestContext.getContext().getToken();
request.getHeaders().add(RequestContext.REQUEST_HEADER_NAME, token);
return execution.execute(request, body);
}
}
Add interceptor to the config
@PostConstruct
public void addInterceptors() {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
interceptors.add(new RestTemplateInterceptor());
restTemplate.setInterceptors(interceptors);
}
Upvotes: 14
Reputation: 318
Basically your token should be located in the header of the request, like for example: Authorization: Bearer . For getting it you can retrieve any header value by @RequestHeader() in your controller:
@GetMapping("/someMapping")
public String someMethod(@RequestHeader("Authorization") String token) {
}
Now you can place the token within the header for the following request:
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);
HttpEntity<RestRequest> entityReq = new HttpEntity<RestRequest>(request, headers);
Now you can pass the HttpEntity to your rest template:
template.exchange("RestSvcUrl", HttpMethod.POST, entityReq, SomeResponse.class);
Hope I could help
Upvotes: 18