Spring security. How to log out user (revoke oauth2 token) - java

When I want to get logout I invoke this code:
request.getSession().invalidate();
SecurityContextHolder.getContext().setAuthentication(null);
But after it (in next request using old oauth token) I invoke
SecurityContextHolder.getContext().getAuthentication();
and I see my old user there.
How to fix it?

Here's my implementation (Spring OAuth2):
#Controller
public class OAuthController {
#Autowired
private TokenStore tokenStore;
#RequestMapping(value = "/oauth/revoke-token", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public void logout(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null) {
String tokenValue = authHeader.replace("Bearer", "").trim();
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
tokenStore.removeAccessToken(accessToken);
}
}
}
For testing:
curl -X GET -H "Authorization: Bearer $TOKEN" http://localhost:8080/backend/oauth/revoke-token

The response by camposer can be improved using the API provided by Spring OAuth. In fact, it's not necessary to access directly to the HTTP headers, but the REST method which removes the access token can be implemented as follows:
#Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;
#Autowired
private ConsumerTokenServices consumerTokenServices;
#RequestMapping("/uaa/logout")
public void logout(Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException {
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) principal;
OAuth2AccessToken accessToken = authorizationServerTokenServices.getAccessToken(oAuth2Authentication);
consumerTokenServices.revokeToken(accessToken.getValue());
String redirectUrl = getLocalContextPathUrl(request)+"/logout?myRedirect="+getRefererUrl(request);
log.debug("Redirect URL: {}",redirectUrl);
response.sendRedirect(redirectUrl);
return;
}
I also added a redirect to the endpoint of Spring Security logout filter, so the session is invalidated and the client must provide credentials again in order to access to the /oauth/authorize endpoint.

It depends on type of oauth2 'grant type' that you're using.
The most common if your have used spring's #EnableOAuth2Sso in your client app is 'Authorization Code'. In this case Spring security redirects login request to the 'Authorization Server' and creates a session in your client app with the data received from 'Authorization Server'.
You can easy destroy your session at the client app calling /logout endpoint, but then client app sends user again to 'authorization server' and returns logged again.
I propose to create a mechanism to intercept logout request at client app and from this server code, call "authorization server" to invalidate the token.
The first change that we need is create one endpoint at the authorization server, using the code proposed by Claudio Tasso, to invalidate the user's access_token.
#Controller
#Slf4j
public class InvalidateTokenController {
#Autowired
private ConsumerTokenServices consumerTokenServices;
#RequestMapping(value="/invalidateToken", method= RequestMethod.POST)
#ResponseBody
public Map<String, String> logout(#RequestParam(name = "access_token") String accessToken) {
LOGGER.debug("Invalidating token {}", accessToken);
consumerTokenServices.revokeToken(accessToken);
Map<String, String> ret = new HashMap<>();
ret.put("access_token", accessToken);
return ret;
}
}
Then at the client app, create a LogoutHandler:
#Slf4j
#Component
#Qualifier("mySsoLogoutHandler")
public class MySsoLogoutHandler implements LogoutHandler {
#Value("${my.oauth.server.schema}://${my.oauth.server.host}:${my.oauth.server.port}/oauth2AuthorizationServer/invalidateToken")
String logoutUrl;
#Override
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
LOGGER.debug("executing MySsoLogoutHandler.logout");
Object details = authentication.getDetails();
if (details.getClass().isAssignableFrom(OAuth2AuthenticationDetails.class)) {
String accessToken = ((OAuth2AuthenticationDetails)details).getTokenValue();
LOGGER.debug("token: {}",accessToken);
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("access_token", accessToken);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "bearer " + accessToken);
HttpEntity<String> request = new HttpEntity(params, headers);
HttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
HttpMessageConverter stringHttpMessageConverternew = new StringHttpMessageConverter();
restTemplate.setMessageConverters(Arrays.asList(new HttpMessageConverter[]{formHttpMessageConverter, stringHttpMessageConverternew}));
try {
ResponseEntity<String> response = restTemplate.exchange(logoutUrl, HttpMethod.POST, request, String.class);
} catch(HttpClientErrorException e) {
LOGGER.error("HttpClientErrorException invalidating token with SSO authorization server. response.status code: {}, server URL: {}", e.getStatusCode(), logoutUrl);
}
}
}
}
And register it at WebSecurityConfigurerAdapter:
#Autowired
MySsoLogoutHandler mySsoLogoutHandler;
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.logout()
.logoutSuccessUrl("/")
// using this antmatcher allows /logout from GET without csrf as indicated in
// https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
// this LogoutHandler invalidate user token from SSO
.addLogoutHandler(mySsoLogoutHandler)
.and()
...
// #formatter:on
}
One note: If you're using JWT web tokens, you can't invalidate it, because the token is not managed by the authorization server.

Its up to your Token Store Implementation.
If you use JDBC token store then you just need to remove it from table...
Anyway you must add /logout endpoint manually then call this :
#RequestMapping(value = "/logmeout", method = RequestMethod.GET)
#ResponseBody
public void logmeout(HttpServletRequest request) {
String token = request.getHeader("bearer ");
if (token != null && token.startsWith("authorization")) {
OAuth2AccessToken oAuth2AccessToken = okenStore.readAccessToken(token.split(" ")[1]);
if (oAuth2AccessToken != null) {
tokenStore.removeAccessToken(oAuth2AccessToken);
}
}

Programmatically you can log out this way:
public void logout(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
new SecurityContextLogoutHandler().logout(request, response, auth);
}
SecurityContextHolder.getContext().setAuthentication(null);
}

Add following line in <http></http> tag.
<logout invalidate-session="true" logout-url="/logout" delete-cookies="JSESSIONID" />
This will delete JSESSIONID and invalidate session. And link to logout button or label would be something like:
Logout
EDIT:
You want to invalidate session from java code. I assume you have to do some task right before logging the user out, and then invalidate session. If this is the use case, you should use custome logout handlers. Visit this site for more information.

This works for Keycloak Confidential Client logout. I have no idea why the folks over at keycloak don't have more robust docs on java non-web clients and their endpoints in general, I guess that's the nature of the beast with open source libs. I had to spend a bit of time in their code:
//requires a Keycloak Client to be setup with Access Type of Confidential, then using the client secret
public void executeLogout(String url){
HttpHeaders requestHeaders = new HttpHeaders();
//not required but recommended for all components as this will help w/t'shooting and logging
requestHeaders.set( "User-Agent", "Keycloak Thick Client Test App Using Spring Security OAuth2 Framework");
//not required by undertow, but might be for tomcat, always set this header!
requestHeaders.set( "Accept", "application/x-www-form-urlencoded" );
//the keycloak logout endpoint uses standard OAuth2 Basic Authentication that inclues the
//Base64-encoded keycloak Client ID and keycloak Client Secret as the value for the Authorization header
createBasicAuthHeaders(requestHeaders);
//we need the keycloak refresh token in the body of the request, it can be had from the access token we got when we logged in:
MultiValueMap<String, String> postParams = new LinkedMultiValueMap<String, String>();
postParams.set( OAuth2Constants.REFRESH_TOKEN, accessToken.getRefreshToken().getValue() );
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(postParams, requestHeaders);
RestTemplate restTemplate = new RestTemplate();
try {
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
System.out.println(response.toString());
} catch (HttpClientErrorException e) {
System.out.println("We should get a 204 No Content - did we?\n" + e.getMessage());
}
}
//has a hard-coded client ID and secret, adjust accordingly
void createBasicAuthHeaders(HttpHeaders requestHeaders){
String auth = keycloakClientId + ":" + keycloakClientSecret;
byte[] encodedAuth = Base64.encodeBase64(
auth.getBytes(Charset.forName("US-ASCII")) );
String authHeaderValue = "Basic " + new String( encodedAuth );
requestHeaders.set( "Authorization", authHeaderValue );
}

Solution provided by user composer perfectly worked for me. I made some minor changes to the code as follows,
#Controller
public class RevokeTokenController {
#Autowired
private TokenStore tokenStore;
#RequestMapping(value = "/revoke-token", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<HttpStatus> logout(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null) {
try {
String tokenValue = authHeader.replace("Bearer", "").trim();
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
tokenStore.removeAccessToken(accessToken);
} catch (Exception e) {
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
}
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
I did this because If you try to invalidate same access token again, it throws Null Pointer exception.

At AuthServer
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
...
endpoints.addInterceptor(new HandlerInterceptorAdapter() {
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
if (modelAndView != null
&& modelAndView.getView() instanceof RedirectView) {
RedirectView redirect = (RedirectView) modelAndView.getView();
String url = redirect.getUrl();
if (url.contains("code=") || url.contains("error=")) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
}
}
}
});
}
At client site
.and()
.logout().logoutSuccessUrl("/").permitAll()
.and().csrf()
.ignoringAntMatchers("/login", "/logout")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
Seems a better solutions for me. referred this link

for logout token with spring boot rest security and oauth2.0
user as follow
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
#RestController
#RequestMapping("/v1/user/")
public class UserController {
#Autowired
private ConsumerTokenServices consumerTokenServices;
/**
* Logout. This method is responsible for logout user from application based on
* given accessToken.
*
* #param accessToken the access token
* #return the response entity
*/
#GetMapping(value = "/oauth/logout")
public ResponseEntity<Response> logout(#RequestParam(name = "access_token") String accessToken) {
consumerTokenServices.revokeToken(accessToken);
return new ResponseEntity<>(new Response(messageSource.getMessage("server.message.oauth.logout.successMessage", null, LocaleContextHolder.getLocale())), HttpStatus.OK);
}
}

You can remove both access token and refresh token from database to save the space.
#PostMapping("/oauth/logout")
public ResponseEntity<String> revoke(HttpServletRequest request) {
try {
String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.contains("Bearer")) {
String tokenValue = authorization.replace("Bearer", "").trim();
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
tokenStore.removeAccessToken(accessToken);
//OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(tokenValue);
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
} catch (Exception e) {
return ResponseEntity.badRequest().body("Invalid access token");
}
return ResponseEntity.ok().body("Access token invalidated successfully");
}

Related

While using Jetty as a server, how do I return a clear json 401 error when JWT Filter blocks unauthenticated users in Spring security?

I have my Spring Security bean which is doing well in blocking unauthorised requests, while using Tomcat, the error response is a clean exception with the message but with Jetty, a text/html is returned even in postman as shown below.
And my doFilterInternal JWT Filter is as below.
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JWTUtility jwtUtility;
#Autowired
private UserServiceImpl userService;
private final ObjectMapper mapper;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String authorization = httpServletRequest.getHeader("Authorization");
String token = null;
String userName = null;
if(null != authorization && authorization.startsWith("Bearer ")) {
token = authorization.substring(7);
userName = jwtUtility.getUsernameFromToken(token);
}
if(null != userName && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails
= userService.loadUserByUsername(userName);
try {
if (jwtUtility.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(userDetails,
null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)
);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
} catch (Exception e){
// System.out.println("Hello world");
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("ACCESS_DENIED", e.getMessage());
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setContentType(String.valueOf(MediaType.APPLICATION_JSON));
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
With Tomcat as my server, everything works as expected, but I have shifted to Jetty Server, how best can I replace this code below to work the same for Jetty?
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("ACCESS_DENIED", e.getMessage());
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setContentType(String.valueOf(MediaType.APPLICATION_JSON));
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
return;
My expected response when the user has not sent in their jwt is as follows:-
HTTP 401 Unauthorized
{
"ACCESS_DENIED": "Some error message here",
}
Otherwise, the request should go through therefore the filter should not return anything.
Note: Please feel free to edit this question to make it better as I have more friends trying to solve the same issue.
The response you are seeing is the default Servlet ERROR dispatch handling (see DispatcherType.ERROR)
Use the standard Servlet error-page mappings to specify your own custom mappings for handling errors. A mapping can be based on status codes, throwables, and more (see Servlet descriptor and <error-page> mappings)

Spring Security authenticate user via post

I have a react app running on a separate port (localhost:3000) that i want to use to authenticate users with, currently i have a proxy setup to my Spring backend (localhost:8080).
Can I somehow manually authenticate instead of http.httpBasic() by sending a POST request to my backend and getting back a session cookie then include the cookie with every request? It would simplify the auth process on iOS side aswell (using this process i could only store the session cookie value in keychain and pass it with every request made to my api)
How would I disable csrf for non-browser requests?
Is there a better approach to this? Diffrent paths for browser and mobile auth?
{
"username": "user",
"password": "12345678"
}
Handle the request in spring controller
#PostMapping(path = "/web")
public String authenticateUser() {
//Receive the auth data here... and perform auth
//Send back session cookie
return "Success?";
}
My WebSecurityConfigurerAdapter.java
#Configuration
#EnableWebSecurity
public class WebsecurityConfig extends WebSecurityConfigurerAdapter {
private final DetailService detailService;
public WebsecurityConfig(DetailService detailService) {
this.detailService = detailService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(detailService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/api/v1/authenticate/new").permitAll()
.antMatchers(HttpMethod.POST,"/api/v1/authenticate/web").permitAll()
.anyRequest().authenticated();
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST").allowedOrigins("http://localhost:8080");
}
};
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(14);
}
}
You can create an endpoint that takes user's credentials in a request body, perform authentication and then set tokens, and other required parameters in HttpOnly cookies.
After setting cookies, subsequent requests can read access/refresh token from cookies and add it in requests, you can then use custom CheckTokenEndpoint to verify tokens.
In the following example TokenParametersDto is a POJO that has username and password properties.
For issuing token (by verifying credentials) you can delegate call to TokenEndpoint#postAccessToken(....) or use its logic to your own method.
#PostMapping(path = "/oauth/http/token", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> issueToken(#RequestBody final #Valid #NotNull TokenParametersDto tokenParametersDto,
final HttpServletResponse response) {
final OAuth2AccessToken token = tokenService.issueToken(tokenParametersDto);
storeTokenInCookie(token, response);
return new ResponseEntity<>(HttpStatus.OK);
}
private void storeTokenInCookie(final OAuth2AccessToken token, final HttpServletResponse response) {
final Cookie accessToken = new Cookie("access_token", token.getValue());
accessToken.setHttpOnly(true);
accessToken.setSecure(sslEnabled);
accessToken.setPath("/");
accessToken.setMaxAge(cookieExpiration);
final Cookie tokenType = new Cookie("token_type", token.getTokenType());
tokenType.setHttpOnly(true);
tokenType.setSecure(sslEnabled);
tokenType.setPath("/");
tokenType.setMaxAge(cookieExpiration);
// Set Refresh Token and other required cookies.
response.addCookie(accessToken);
response.addCookie(tokenType);
}
Check this answer for disabling CSRF for a specific URL section.

User's OAuth2 Token into RestTemplate

How to correctly get the users's session oauth2 token ?
I implemented an OAuth2 Authorization/Resource server using spring-security-oauth2-autoconfigure.
I implemented a client app, that uses the authorization server to login the user and gets his access token. The login phase is working perfectly and so the retreive of the login data (using the access token by the oauth2 filters). The Principal in the client app requests correctly shows all authorities filled by the authorization server.
I'd like to use the client app as a proxy to send Rest Request using the given Access Token of the user that requested the call.
I already tried to use #EnableOAuth2Client but that does not work. The OAuth2RestTemplate is null when tried to be autowired.
I had to reimplement a request scoped bean of a RestTemplate which get the tokenValue from the SecurityContext. This works, but I do not find this clean. This behavior is quite common, so I should miss something.
application.yml
spring:
application.name: client
security:
oauth2:
client:
registration:
myclient:
client-id: client-id
client-secret: client-secret
redirect-uri: http://localhost:8081/login/oauth2/code/
authorization-grant-type: authorization_code
provider:
myclient:
authorization-uri: http://localhost:8090/oauth/authorize
token-uri: http://localhost:8090/oauth/token
user-info-uri: http://localhost:8090/me
user-name-attribute: name
SecurityConfiguration
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests()
.antMatchers("/", "/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().oauth2Login()
.and().oauth2Client()
;
// #formatter:on
}
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
#Qualifier("oauth2RestTemplate")
public RestTemplate oauth2RestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new ClientHttpRequestInterceptor() {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated() && auth instanceof OAuth2AuthenticationToken) {
#SuppressWarnings("unchecked")
Map<String, Object> details = (Map<String, Object>) ((OAuth2AuthenticationToken) auth).getPrincipal().getAttributes().get("details");
String tokenValue = (String) details.get("tokenValue");
if (tokenValue != null) {
request.getHeaders().add("Authorization", "Bearer " + tokenValue);
}
}
return execution.execute(request, body);
}
});
return restTemplate;
}
}
In WebController
private #Autowired #Qualifier("oauth2RestTemplate") RestTemplate oauth2RestTemplate;
#GetMapping("/remote")
public Map<String, Object> remote() {
#SuppressWarnings("unchecked")
Map<String, Object> resp = oauth2RestTemplate.getForObject(URI.create("http://localhost:8090/api/test"), Map.class);
return resp;
}
It works, but I do not think I should configure the RestTemplate myself.
Unfortunately, you have to define OAuth2RestTemplate. However, this is a more clean implementation.
#Bean
public OAuth2RestTemplate oauth2RestTemplate() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setAccessTokenUri(format("%s/oauth/token", authServerUrl));
resourceDetails.setClientId("client_id");
resourceDetails.setClientSecret("client_secret");
resourceDetails.setGrantType("client_credentials");
resourceDetails.setScope(asList("read", "write"));
DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
return new OAuth2RestTemplate(resourceDetails, clientContext);
}
In this case, your Resource server will communicate with the authorization server on your behalf using its own credentials.

Why is Authorization Header missing?

I have Eureka and connected services Zuul:8090, AuthService:[any_port].
I send ../login request to Zuul he send to AuthSercice. Then AuthSerice put into Header JWT Authentication.
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String token = Jwts.builder()
.setSubject( ((User) authResult.getPrincipal()).getUsername())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
response.addHeader("Authorization", "Bearer "+ token); // this is missing
response.addHeader("Authorization2", "Bearer " + token); // ok
}
I do request on Postman. Request result
First I tried to use JWT in Monoliths. There wasn't any problem, and Authorization Token can be added.
Why is Authorization Header missing?
It is because of a built-in mechanism in Zuul -- it automatically filters out sensitive headers, such as Authorization and Cookies, to protect sensitive information from being forwarded to downstream services.
That is why you can not get the header with the name Authorization.
if you want your downstream services to receive them anyway, just define the filter by yourself in your Zuul config file, instead of using default.
zuul:
routes:
users:
path: your url pattern
sensitiveHeaders: //put nothing here!! leave it blank, the filter will be off
url: downstream url
Here is spring official explanation on sensitive headers: document
You need set the option for forwarding headers in Eureka.
For Login I would suggest to have a custom ZuulFilter.
public abstract class AuthenticationZuulFilter extends ZuulFilter {
private static final Log logger = getLog(AuthenticationZuulFilter.class);
private static final String BEARER_TOKEN_TYPE = "Bearer ";
private static final String PRE_ZUUL_FILTER_TYPE = "pre";
private AuthTokenProvider tokenProvider;
public AuthenticationZuulFilter(AuthTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
#Override
public Object run() {
RequestContext ctx = getCurrentContext();
ctx.addZuulRequestHeader(X_USER_INFO_HEADER_NAME, buildUserInfoHeaderFromAuthentication());
ctx.addZuulRequestHeader(AUTHORIZATION, BEARER_TOKEN_TYPE + tokenProvider.getToken());
return null;
}
#Override
public String filterType() {
return PRE_ZUUL_FILTER_TYPE;
}
#Override
public int filterOrder() {
return 1;
}
This is an implementation of it can be like this.
#Component
public class UserAuthenticationZuulFilter extends AuthenticationZuulFilter {
#Value("#{'${user.allowed.paths}'.split(',')}")
private List<String> allowedPathAntPatterns;
private PathMatcher pathMatcher = new AntPathMatcher();
#Autowired
public UserAuthenticationZuulFilter (AuthTokenProvider tokenProvider) {
super(tokenProvider);
}
#Override
public boolean shouldFilter() {
Authentication auth = getContext().getAuthentication();
HttpServletRequest request = getCurrentContext().getRequest();
String requestUri = request.getRequestURI();
String requestMethod = request.getMethod();
return auth instanceof UserAuthenticationZuulFilter && GET.matches(requestMethod) && isAllowedPath(requestUri);
}
}

Propagate HTTP header (JWT Token) over services using spring rest template

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?
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
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);
}
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
}
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;
}

Categories