Spring OAuth 2 + JWT Inlcuding additional info JUST in access token - spring

I am able to include additional information into the access token implementing my own TokenEnhancer, but such info is included twice. One in the encoded access_token, and the other in the auth server response.
Long story short! I request an access token with right credentials, and I get this response:
{
"access_token" : "eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRJZCI6Ik1ZX0NVU1RPTV9JTkZPX0NMSUVOVCIsInVzZXJfbmFtZSI6IlVTRVIiLCJzY29wZSI6WyJGT08iXSwiZXhwIjoxNTA2MzkwOTM5LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiZjJkYWFkM2ItYzkzOC00ZjExLWI3ODctMzExZDdlNjYzYzhhIiwiY2xpZW50X2lkIjoid2ViX2FwcCJ9.IdgYRxwZGRPR97nxHpAcJXNWDTShQE1tsg9NsBwlOk8eDWE1B-mjfGTaKiyTO1-m9GBpXnxt2PaOV7AbdLsCZ5xLPUR0_5ehuNB6WCXLSkdac5xbw-rmNdJHTe9gLJizOZAKF6J-_Xo9OOQISKBqliY5vo5y0btqIw4CX6-ukYoWZmwHThwnAsEA_PqGuEXsbXMGz-vqJaSVpvJeEOBNL0KOh-cNxc0ft-rJ3snjPerN_efAiZdFkzxdCeuoGmZvSyHRjYR8kQ3ZqZ5MOunw9YuTvidL1IK5TODHQ2BjiCTpbgDlYx-Oh5UxcYNrPOhD-tBjRuuqDSz8K6ddpke4RQ",
"token_type" : "bearer",
"refresh_token" : "eyJhbGciOiJSUzI1NiJ9.eyJjbGllbnRJZCI6Ik1ZX0NVU1RPTV9JTkZPX0NMSUVOVCIsInVzZXJfbmFtZSI6IlVTRVIiLCJzY29wZSI6WyJGT08iXSwiYXRpIjoiZjJkYWFkM2ItYzkzOC00ZjExLWI3ODctMzExZDdlNjYzYzhhIiwiZXhwIjoxNTA4OTM5NzM5LCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiOGU2Zjc0OTEtMmQ3MC00NTUwLThhMDgtZjk0YjkzYTVkYWZmIiwiY2xpZW50X2lkIjoid2ViX2FwcCJ9.MqwMrYrofu7pUQu2mF33__h6M4OWSRrQ-lc8JzTn0DkpJ6a3-yjnjjppZ9fs3KBz_lpRIO8jo--eId449rEjP4M3_9lDRSW9_HyBAvd57OtyUHa5SPM9prD6ReXGCyiIw2gO07euIf-Vp4UHsjoKK0MdtfMmFIWms1JMGFBmzBha8kqKaMxKzppGy-jVdP7384K9oovD20H-NubjScfoO2Crp1cTM-SXc-0v6kwB1qV-cI6HKXmbkoFhbH2bL_nRvXTkLYI-UvRNTNLHzqhcqztLTrszcWa2BjNU2IofsNByFS8BHTDV1vu0BqZA4kfNCJcFJ89tBDt2L8vfFkYezQ",
"expires_in" : 43199,
"scope" : "FOO",
"clientId" : "MY_CUSTOM_INFO_CLIENT",
"jti" : "f2daad3b-c938-4f11-b787-311d7e663c8a"
}
So I can see clientId included in the response... Now I copy my access_token and I decoded in: https://jwt.io/
And in the payload is included also the clientId...
My question is: How can I remove the addional info from the server response and leave it just in the tokens (access_token and refresh_token).
Please see below the code:
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager)
.tokenEnhancer(tokenEnhancerChain);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("web_app")
.secret("web_app123")
.scopes("FOO")
.autoApprove(true)
.authorities("FOO_READ", "FOO_WRITE")
.authorizedGrantTypes("refresh_token", "password");
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(new ClassPathResource("mykey.jks"), "mykey123".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mykey"));
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
}
And my CustomTokenEnhancer:
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import com.mapflow.ms.security.service.UserDetailInfo;
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
UserDetailInfo user = (UserDetailInfo) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<String, Object>();
additionalInfo.put("clientId", user.getClientId());
((DefaultOAuth2AccessToken) accessToken)
.setAdditionalInformation(additionalInfo);
return accessToken;
}
}

After a while I figured it out. JwtAccessTokenConverter implements TokenEnhaner too. First CustomTokenEnhaner.enhance is called, including the additional information. Then JwtAccessTokenConverter.enhance, encoding the AccessToken by CustomTokenEnhaner.enhance and including addional information to the response. The idea is initialize DefaultOAuth2AccessToken.additionalInformation once is encoded in the access_token. Solution is:
First let CustomTokenEnhancer extends JwtAccessTokenConverter, override enhance, attach additional information, call enhance from the parent and initialize the DefaultOAuth2AccessToken.additionalInformation:
public class CustomTokenConverter extends JwtAccessTokenConverter {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
if(authentication.getOAuth2Request().getGrantType().equalsIgnoreCase("password")) {
UserDetailInfo user = (UserDetailInfo) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<String, Object>();
additionalInfo.put("clientId", user.getClientId());
((DefaultOAuth2AccessToken) accessToken)
.setAdditionalInformation(additionalInfo);
}
accessToken = super.enhance(accessToken, authentication);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(new HashMap<>());
return accessToken;
}
}
And last step, would be delete the bean
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(new ClassPathResource("mykey.jks"), "mykey123".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mykey"));
return converter;
}
And add the key to the CustomTokenEnhancer
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
CustomTokenConverter tokenConverter = new CustomTokenConverter();
tokenConverter.setSigningKey("PswMapview2017");
return tokenConverter;
}
That would be it.

Related

Spring Boot 3 + Spring Security 6 => 403 Forbidden with "requestMatchers"

For several days now I have been trying to solve a problem with Spring Security 6. I've read almost all the spring documentation for Spring Security 6 and I watched several tutorials and just cannot see where the mistake is. I looked at the code under a magnifying glass:
WebSecurityConfigurer.class:
package com.transfer.market.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
#Configuration
#EnableWebSecurity
public class WebSecurityConfigurer {
private final String ADMIN;
private final String ADMIN_PASS;
private final String SUPER;
private final String SUPER_PASS;
#Autowired
public WebSecurityConfigurer(AppSecurityExternalConfig appSecurityExternalConfig) {
this.ADMIN = appSecurityExternalConfig.getUser().getAdmin();
this.ADMIN_PASS = appSecurityExternalConfig.getPassword().getAdmin();
this.SUPER = appSecurityExternalConfig.getUser().getSup();
this.SUPER_PASS = appSecurityExternalConfig.getPassword().getSup();
}
#Bean
public UserDetailsService users() {
UserDetails admin = User.builder()
.username(ADMIN)
.password(encoder().encode(ADMIN_PASS))
.roles("ADMIN")
.build();
UserDetails sup = User.builder()
.username(SUPER)
.password(encoder().encode(SUPER_PASS))
.roles("ADMIN", "DBA")
.build();
return new InMemoryUserDetailsManager(admin, sup);
}
#Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/resource/**").permitAll()
.requestMatchers("/api/**").hasAnyRole("ADMIN", "DBA")
.requestMatchers("/db/**")
.access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')"))
.anyRequest().denyAll()
);
return http.build();
}
#Bean
public static BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}
I have printed ADMIN, SUPER and their passwords to the console and they are for sure read correctly from the application.properties. So that is not the problem.
AppSecurityExternalConfig.class:
package com.transfer.market.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
#Data
#Configuration
#ConfigurationProperties(prefix = "config.security")
public class AppSecurityExternalConfig {
private User user;
private Password password;
#Data
#Component
#ConfigurationProperties(prefix = "user")
public static class User {
private String user;
private String admin;
private String sup;
}
#Data
#Component
#ConfigurationProperties(prefix = "password")
public static class Password {
private String user;
private String admin;
private String sup;
}
}
application.properties:
...
# Security:
config.security.user.admin=admin
config.security.password.admin=pass
config.security.user.sup=super
config.security.password.sup=pass
...
PlayerController.class:
#RestController
#Validated
public class PlayerController {
private final PlayerService playerService;
#Autowired
public PlayerController(PlayerService playerService) {
this.playerService = playerService;
}
#PostMapping("/api/players")
public ResponseEntity<Player> addSingle(#RequestBody #Valid Player player) {
return new ResponseEntity<>(playerService.addSingle(player), HttpStatus.CREATED);
}
...
It just keeps getting "403 Forbidden", but for all end points starting with "/resource" where they are .permitAll() it works and it's 200 OK. Why doesnt the other requestMatchers work? Please help.
#Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth.requestMatchers("/resource/**").permitAll()
.requestMatchers("/api/**")
.hasAnyRole("ADMIN", "DBA")
.requestMatchers("/db/**")
.access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and
hasRole('DBA')"))
.anyRequest().denyAll());
return http.build();
}
You have to configured the basic authentication. Add the following statement in the SecurityFilterChain bean.
http.httpBasic();
i.e.
#Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth.requestMatchers("/resource/**").permitAll()
.requestMatchers("/api/**")
.hasAnyRole("ADMIN")
.requestMatchers("/db/**")
.access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')"))
.anyRequest().denyAll());
http.httpBasic();
return http.build();
}

How to get BearerTokenAuthentication from SecurityContext in a GlobalFilter in Spring Cloud Gateway

Spring Cloud Gateway as a OAuth2ResourceServer with following Authorisation Config:
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges ->
exchanges
.anyExchange().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt)
return http.build();
}
I have a global filter in place which is responsible for performing some functions at each valid authenticated request, something like this:
#Service
public class CustomGlobal implements GlobalFilter {
#Autowired
BearerTokenAuthentication authentication;
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// access request headers, and perform some logic
// extract details from the JWT token, and perform some logic
log.info(authentication.getTokenAttributes.get("sub"));
// ^ in the above line there's a NullPointerException, since instance
// BearerTokenAuthentication is not set, or not visible at a GlobalFilter class
return chain.filter(exchange);
}
}
I am still in a learning phase. Any possible leads would be appreciated.
I did that this way(Note you should change WebFilter to GlobalFilter).
Add into your pom
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.4.6</version>
</dependency>
Then Filter should be like
package filter;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
#Log4j2
public class CustomGlobal implements WebFilter {
public static final String HEADER_PREFIX = "Bearer ";
private final ReactiveJwtDecoder jwtDecoder;
public ReactiveJwtDecoder createDecoder(String issuer, String jwkUrl) {
var jwtDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl).build();
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
new JwtIssuerValidator(issuer),
new JwtTimestampValidator()));
return jwtDecoder;
}
protected CustomGlobal(String issuer, String jwkUrl) {
this.jwtDecoder = createDecoder(issuer, jwkUrl);
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return Mono
.defer(() -> {
var token = resolveToken(exchange.getRequest());
if (!StringUtils.hasText(token)) {
throw new BadJwtException("Authorisation token is invalid");
}
return jwtDecoder.decode(token);
})
.flatMap(tokenJwt -> {
log.info(tokenJwt.getClaimAsString("sub"));
return chain.filter(exchange);
})
.onErrorResume(err -> handleError(exchange));
}
private Mono<Void> handleError(ServerWebExchange exchange) {
exchange.getResponse().setRawStatusCode(HttpStatus.UNAUTHORIZED.value());
exchange.getResponse().getHeaders().add("Content-Type", "application/json");
return exchange.getResponse().setComplete();
}
private String resolveToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(HEADER_PREFIX)) {
return bearerToken.substring(7).trim();
}
return "";
}
}
Next step would be to create configuration
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
#Configuration
public class CustomGlobalConfig {
#Value("${jwt.iss}")
private String issuer;
#Value("${jwt.jwk-uri}")
private String jwkUrl;
#Bean
CustomGlobal createFilterBean() {
return new CustomGlobal(this.issuer, this.jwkUrl);
}
}

How to fix "Bad credentials" error using authentication manager?

I'm doing a project in which you have to register some users and also giving them a rol (user by default). After I've registered a user, I added jwt auth and I was able to get the jwt response, but after trying to implement some filters on it, the code started to fail.
At this point I've commented the filter implementation method and also inside my WebSecurityConfig.
It's supposed that I'll receive a username and password inside my endpoint ("authenticate/").
#RequestMapping(
value = {"authenticate", "authenticate/"},
method = RequestMethod.POST,
consumes = {
MediaType.APPLICATION_FORM_URLENCODED_VALUE,
MediaType.MULTIPART_FORM_DATA_VALUE,
MediaType.APPLICATION_JSON_VALUE
},
produces = MediaType.APPLICATION_JSON_VALUE
)
public #ResponseBody AuthResponse login(AuthRequest authRequest) throws Exception
{
return this.authService.authenticate(authRequest);
}
AuthRequest is username and password.
#AllArgsConstructor
#NoArgsConstructor
#Setter
#Getter
public class AuthResponse
{
private String username;
private String password;
}
But the error is when I try to authenticate the user using the AuthenticationManager.
AuthService authenticate method
public AuthResponse authenticate(AuthRequest authRequest) throws Exception
{
//System.out.println(authRequest.getUsername() +"," + authRequest.getPassword() + "," + this.bCryptPasswordEncoder.encode(authRequest.getPassword()));
try {
this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
} catch (BadCredentialsException e) {
//Here is the error
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = usersDetailService.loadUserByUsername(authRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails);
//this.logService.save(new Log(null, userDetails.getUsername(), jwt, null));
System.out.println(jwt);
return new AuthResponse(jwt);
}
Error inside postman console.
"timestamp": "2021-04-03T19:23:01.510+00:00",
"status": 403,
"error": "Forbidden",
"trace": "java.lang.Exception: Incorrect username or password\n\tat com.cncipo.nl.auth.service.AuthService.authenticate2(AuthService.java:60)\n\tat com.cncipo.nl.controller.SessionRestController.auth(SessionRestController.java:85)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:652)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327)\n\tat org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)\n\tat org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)\n\tat org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)\n\tat org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:105)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)\n\tat org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)\n\tat org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)\n\tat org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)\n\tat org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)\n\tat org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)\n\tat org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.lang.Thread.run(Thread.java:748)\nCaused by: org.springframework.security.authentication.BadCredentialsException: Bad credentials\n\tat org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:141)\n\tat org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)\n\tat org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201)\n\tat org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:518)\n\tat com.cncipo.nl.auth.service.AuthService.authenticate2(AuthService.java:56)\n\t... 86 more\n",
"message": "Access Denied",
"path": "/api/authenticate"
}
I'll add the WebSecurityConfig and the JwtFilter in case there is something that I missed and it's causing the error.
JwtFilter
//#Component
public class JwtRequestFilter extends OncePerRequestFilter
{
#Autowired
private UsersDetailService usersDetailService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
{
/* Get the header for auth */
final String authHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
System.out.println("Flag of header it's null when I implemented" + authHeader);
/* Extract jwt and username */
if (authHeader != null && authHeader.startsWith("Bearer "))
{
jwt = authHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
/* Extract user details */
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
{
UserDetails userDetails = this.usersDetailService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails))
{
/* Create default new jwt auth token */
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
/* Handling the control to the next filter chain */
filterChain.doFilter(request, response);
}
}
WebSecurityConfig
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private UsersDetailService usersDetailService;
//#Autowired
//private JwtRequestFilter jwtRequestFilter;
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(usersDetailService);
}
/* Bean of AuthManager due to it used to work in older version of spring */
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
/* Disable security */
http.csrf().disable();
/* User info page requires login as admin and user role. If no login, redirect */
http.authorizeRequests().antMatchers("users").access("hasAnyRole('admin', 'user')");
/* Only for admin */
http.authorizeRequests().antMatchers("logs", "admin").access("hasRole('admin')");
/* The pages does not require login/auth */
http.authorizeRequests().antMatchers("login", "logout", "/authenticate/, login/")
.permitAll();//.anyRequest().authenticated();
/* Using sessiong managment for intercept jwt auth . Spring won't create session, jwt will manage them*/
//http.exceptionHandling().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
UsersDetailService, MyUserDetails implements UserDetails from import org.springframework.security.core.userdetails.UserDetails;
#Service
public class UsersDetailService implements UserDetailsService
{
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
User user = this.userRepository.getUserByUsername_user(username);
if (user == null) { throw new UsernameNotFoundException("Could not find user"); }
return new MyUserDetails(user);
}
}
Try the following settings:
In your SecurityConfig
#Autowired
private JwtFilterRequest jwtFilterRequest;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/forgetPassword").permitAll()
.antMatchers("/registerUser").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().formLogin().loginPage("/login").loginProcessingUrl("/login")
.defaultSuccessUrl("/index.html").failureUrl("/login?error")
.and().logout().logoutUrl("/logout");
http.addFilterBefore(this.jwtFilterRequest, UsernamePasswordAuthenticationFilter.class);
}
Filter Class JWT (JwtFilterRequest):
import cl.project.admin.security.jwt.JwtUtil;
import cl.project.admin.security.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class JwtFilterRequest extends OncePerRequestFilter {
#Autowired
private JwtUtil jwtUtil;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
String jwt = authorizationHeader.substring(7);
String username = this.jwtUtil.extractUsername(jwt);
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if(this.jwtUtil.validateToken(jwt, userDetails)){
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
}
filterChain.doFilter(request, response);
}
}
Your Custom UserDetailService:
import cl.project.admin.controllers.AttentionController;
import cl.project.admin.models.dao.IUserDao;
import cl.project.admin.models.entity.User;
import cl.project.admin.models.service.UserServiceImpl;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
#Service
public class CustomUserDetailsService implements UserDetailsService {
Logger logger = Logger.getLogger(CustomUserDetailsService.class);
#Autowired
private UserServiceImpl userService;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if(user == null) {
this.logger.error("Error al ingresar: " + username);
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());
}
}
JWT Util
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
#Service
public class JwtUtil {
#Value("${keyJWTSpringSecurity}")
private static final String KEY = "passwordInYourApplicationProperties";
public String generateToken(UserDetails userDetail){
return Jwts.builder().setSubject(userDetail.getUsername()).setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000*60*60*5))
.signWith(SignatureAlgorithm.HS256, KEY).compact();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
public String generateTokenInfinity(String username) {
Map<String, Object> claims = new HashMap<>();
return createTokenInfinity(claims, username);
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 5))
.signWith(SignatureAlgorithm.HS256, KEY).compact();
}
private String createTokenInfinity(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 800))
.signWith(SignatureAlgorithm.HS256, KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}

OAuth2, Access /oauth2/token ressource behind a proxy

I need to consume an API securised by OAuth2 with WebClient. I have configure the OAuth2AuthorizedClientManager to manage the access token and refresh it when it need to be.
However I encounter some issue, java.net.UnknownHostException. There is a proxy between my Application and the OAuth2 token ressource and I do not know how to configure it.
What I have try :
Test it in an other environment without proxy and it's work. My OAuth2AuthorizedClientManager configuration is correct.
System.setProperty(), not a solution, I have several proxy to manage.
maybe I am misunderstanding some OAuth2 notions
Here some code:
application.properties
spring.security.oauth2.client.registration.client.client-id=clientId
spring.security.oauth2.client.registration.client.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.client.client-secret=clientSecret
spring.security.oauth2.client.provider.client.token-uri=URI/oauth2/token
WebClientConfig
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService clientService)
{
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, clientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
#Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("client");
return WebClient.builder()
.baseUrl("URI")
.clientConnector(getReactorClientHttpConnector(url))
.apply(oauth2Client.oauth2Configuration())
.build();
}
My test
#Autowired
WebClient webClient;
public void test() {
RequestHeadersSpec<?> request = webClient.get()
.uri("/heartbeats");
}
Error
org.springframework.security.oauth2.core.OAuth2AuthorizationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: I/O error on POST request for "URI/oauth2/token": URI; nested exception is java.net.UnknownHostException: URI
My question is, How to configure a proxy for the OAuth2AuthorizedClientManager ?
Please feel free to ask for clarification.
Any help would be appreciated. Thanks
We had some similar problem in the past and solved it by following configuration.
#Configuration
public class AuthConfiguration {
#Bean
public JwtDecoderFactory<ClientRegistration> jwtDecoderFactory() {
return new CustomOidcIdTokenDecoderFactory(jwksRestTemplate());
}
#Bean
public DefaultAuthorizationCodeTokenResponseClient oAuth2AccessTokenResponseClient() {
var defaultAuthorizationCodeTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
defaultAuthorizationCodeTokenResponseClient.setRestOperations(tokenRestTemplate());
return defaultAuthorizationCodeTokenResponseClient;
}
#Bean
public RestTemplate jwksRestTemplate() {
return new RestTemplate(requestFactory());
}
#Bean
public RestTemplate tokenRestTemplate() {
// Copied from constructor of DefaultAuthorizationCodeTokenResponseClient
var restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
restTemplate.setRequestFactory(requestFactory());
return restTemplate;
}
private ClientHttpRequestFactory requestFactory() {
var requestFactory = new SimpleClientHttpRequestFactory();
var proxy = new Proxy(Type.HTTP, new InetSocketAddress("my.host.com", 8080));
requestFactory.setProxy(proxy);
return requestFactory;
}
}
Maybe this helps. Also the following class needs to be added because it is package private in Spring ;)
package org.springframework.security.oauth2.client.oidc.authentication;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.client.oidc.authentication.DefaultOidcIdTokenValidatorFactory;
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
import java.util.function.Function;
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri;
/**
* extension for {#link OidcIdTokenDecoderFactory} to mock the JWKS request
*/
public class CustomOidcIdTokenDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters());
private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256;
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = new DefaultOidcIdTokenValidatorFactory();
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
private final RestTemplate restTemplate;
public CustomOidcIdTokenDecoderFactory(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
NimbusJwtDecoder jwtDecoder = buildDecoder(clientRegistration);
jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(clientRegistration));
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
this.claimTypeConverterFactory.apply(clientRegistration);
if (claimTypeConverter != null) {
jwtDecoder.setClaimSetConverter(claimTypeConverter);
}
return jwtDecoder;
}
private NimbusJwtDecoder buildDecoder(ClientRegistration clientRegistration) {
JwsAlgorithm jwsAlgorithm = this.jwsAlgorithmResolver.apply(clientRegistration);
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
if (!StringUtils.hasText(jwkSetUri)) {
OAuth2Error oauth2Error = new OAuth2Error(
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
"Failed to find a Signature Verifier for Client Registration: '" +
clientRegistration.getRegistrationId() +
"'. Check to ensure you have configured the JwkSet URI.",
null
);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
return withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).restOperations(restTemplate).build();
}
}

How to get jwt token string on service layer when user request first time using jwt, Oauth2, spring security?

I am new in development of micro-services with jwt.
Here is my project structure:
First micro-service is used to authenticate users by jwt and Oauth2
using username and password. This microservice is called auth-service.
Login request url is like:
[http://localhost:9092/oauth/token?username=s#sawai.com&password=randomPassword&grant_type=password][1]
By calling this url we got jwt token like:
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyX2lkIl0sInVzZXJfbmFtZSI6InNAc2F3YWkuY29tIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl0sInRlbmFudElkIjoic2F3YWkuY29tIiwic3lzdGVtR2VuZXJhdGVkUGFzc3dvcmQiOnRydWUsImlkIjoiNTYzOTFhYzAtZDc4OC00ODEyLThmYWMtODEwZTIyMjdjYmI1IiwiZXhwIjoxNTI0NzMxNzgwLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6ImY0ZTNmNTM5LWRkNDgtNGMxMy05OTg5LTcwM2E1NWYxMjNlYyIsImVtYWlsIjoic0BzYXdhaS5jb20iLCJjbGllbnRfaWQiOiJ0cnVzdGVkLWFwcCJ9.AS1tXpUcPMgEw63FrvPP-xGBz7qCi14Eqe29rDzTXPg","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyX2lkIl0sInVzZXJfbmFtZSI6InNAc2F3YWkuY29tIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl0sImF0aSI6ImY0ZTNmNTM5LWRkNDgtNGMxMy05OTg5LTcwM2E1NWYxMjNlYyIsInRlbmFudElkIjoic2F3YWkuY29tIiwic3lzdGVtR2VuZXJhdGVkUGFzc3dvcmQiOnRydWUsImlkIjoiNTYzOTFhYzAtZDc4OC00ODEyLThmYWMtODEwZTIyMjdjYmI1IiwiZXhwIjoxNTI0NzQ2MTgwLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjI1ZmJlY2IyLWRhODgtNDY1ZS1iM2I2LTFlN2NmYjBlYmVjMiIsImVtYWlsIjoic0BzYXdhaS5jb20iLCJjbGllbnRfaWQiOiJ0cnVzdGVkLWFwcCJ9.jSG5zUBzu9yGqnBueU7fkIZV6XhXD8oCkYCerwHkkOw","expires_in":14399,"scope":"read write","tenantId":"sawai.com","systemGeneratedPassword":true,"id":"56391ac0-d788-4812-8fac-810e2227cbb5","email":"s#sawai.com","jti":"f4e3f539-dd48-4c13-9989-703a55f123ec"}
On auth service database we just create a single table called users with following fields:
id varchar(255)
created_on datetime
last_modified_date datetime
email varchar(255)
enabled bit(1)
password varchar(255)
role varchar(255)
system_generated_password bit(1)
tenant_id varchar(255)
validate bit(1)
OK.
Now another micro-service is called company and in company service we have list of users(not same as auth service users, because auth service contains users for multiple micro-services like: Company, Candidate etc).
Now we want to maintain last_logged_on for company users. So admin can check when an user logged in last time.
What we try to do is: When user login using auth service and user type is company user then call company service and update users last_logged_on. For calling company service we need jwt-access-token because urls are secure on company side. So how can we get access token on auth service when we request to get jwt token.
Here is configuration for jwt with spring boot on auth side.
package com.cs.je.auth.config.jwt;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import com.cs.je.auth.enums.Role;
import com.cs.je.auth.model.User;
/**
* #author sawai
*
*/
#Configuration
#EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
#Value("${auth.token.time}")
private int accessTokenValiditySeconds;
#Value("${refresh.token.time}")
private int refreshTokenValiditySeconds;
#Value("${security.oauth2.resource.id}")
private String resourceId;
#Value("${trusted.app.name}")
private String trustedAppName;
#Value("${trusted.app.secret}")
private String trustedAppSecret;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private CustomAccessTokenConverter customAccessTokenConverter;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(this.authenticationManager)
.tokenServices(tokenServices())
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
// we're allowing access to the token only for clients with 'ROLE_TRUSTED_CLIENT' authority
//.tokenKeyAccess("permitAll()")
.tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(trustedAppName)
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.authorities("ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.resourceIds(resourceId)
// .accessTokenValiditySeconds(accessTokenValiditySeconds)
// .refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.secret(trustedAppSecret);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
System.out.println("3333333333333");
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
tokenConverter.setUserTokenConverter(new DefaultUserAuthenticationConverter() {
#Override
public Authentication extractAuthentication(Map<String, ?> map) {
Authentication authentication = super.extractAuthentication(map);
System.out.println("222222222222");
// User is my custom UserDetails class
User user = new User();
user.setTenantId(map.get("tenantId").toString());
user.setEmail(map.get("email").toString());
user.setId(map.get("id").toString());
//user.setPassword(map.get("password").toString());
//System.out.println("date " + );
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
authorities.addAll(authentication.getAuthorities());
user.setGrantedAuthorities(authorities);
user.setRole(Role.valueOf(authorities.iterator().next().toString()));
//user.setSpecialKey(map.get("specialKey").toString());
return new UsernamePasswordAuthenticationToken(user,
authentication.getCredentials(), authentication.getAuthorities());
}
});
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("key");
converter.setAccessTokenConverter(customAccessTokenConverter);
converter.setAccessTokenConverter(tokenConverter);
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
defaultTokenServices.setRefreshTokenValiditySeconds(refreshTokenValiditySeconds);
defaultTokenServices.setReuseRefreshToken(false);
defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
return defaultTokenServices;
}
}
Here is Custom Token Enhance:
package com.cs.je.auth.config.jwt;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import com.cs.je.auth.enums.Role;
import com.cs.je.auth.model.User;
import com.cs.je.auth.rest.api.call.CustomRestTemplate;
import com.cs.je.auth.service.UserService;
import com.cs.je.auth.utils.EncTokenUtils;
import com.cs.je.auth.utils.UserUtils;
/**
* #author sawai
*
*/
#Component
public class CustomTokenEnhancer implements TokenEnhancer {
#Autowired
private UserService userService;
#Autowired
private CustomRestTemplate customRestTemplate;
#Value("${microservice.company.protocol}")
private String protocol;
#Value("${microservice.company.port}")
private String port;
#Value("${microservice.company.ip}")
private String ipAddress;
#Value("${microservice.company.user.api}")
private String userCreateUrl;
#Autowired
private TokenStore tokenStore;
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
if (authentication != null) {
User user = (User)authentication.getPrincipal();
additionalInfo.put("email", user.getEmail());
additionalInfo.put("tenantId", user.getTenantId());
additionalInfo.put("id", user.getId());
if (user.isSystemGeneratedPassword()) {
additionalInfo.put("systemGeneratedPassword", true);
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
System.out.println(accessToken.getRefreshToken());
System.out.println(accessToken.getRefreshToken().getValue());
System.out.println(accessToken.getTokenType());
/*if (user.getRole().equals(Role.ROLE_ADMIN) || user.getRole().equals(Role.ROLE_USER)) {
String token = accessToken.toString();
try {
System.out.println("before call");
//ResponseEntity<Object> responseEntity = customRestTemplate.exchange(protocol + "://" + ipAddress + ":" + port + userCreateUrl + "/" + user.getId() + "/last-loggedOn", token, EncTokenUtils.getEncToken(user.getEmail()), HttpMethod.PUT, null);
System.out.println("successfull");
} catch (Exception e) {
e.printStackTrace();
}
}*/
System.out.println("1111111111");
}
return accessToken;
}
}
Security Config
package com.cs.je.auth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.cs.je.auth.constant.Constants;
import com.cs.je.auth.enums.Role;
import com.cs.je.auth.model.User;
import com.cs.je.auth.repository.UserRepository;
import com.cs.je.auth.service.UserService;
/**
* #author sawai
*
*/
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private UserRepository userRepository;
/*#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST ,"/user/**").hasAnyAuthority("ROLE_SUPER_USER", "ROLE_ADMIN")
.anyRequest().authenticated();
httpSecurity.csrf().disable();
httpSecurity.headers().frameOptions().disable();
httpSecurity.requestCache().requestCache(new NullRequestCache());
httpSecurity.httpBasic();
//httpSecurity.addFilterBefore(CORSFilter, ChannelProcessingFilter.class);
}*/
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
if (userRepository.count() < 1) {
User user = new User();
user.setEmail("jeAdmin#email.com");
user.setPassword("jeAdminUser");
user.setTenantId(Constants.JE_TENANT_ID);
user.setRole(Role.ROLE_SUPER_USER);
user.setValidate(true);
userService.create(user, null);
}
}
#Override
public void configure(WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers("/api/candidate/**");
webSecurity.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
webSecurity.ignoring().antMatchers("/api/company/**");
webSecurity.ignoring().antMatchers("/api/forgotPassword/**");
//webSecurity.ignoring().antMatchers(HttpMethod.POST, "/oauth/**");
}
}
ResourceConfig.java
package com.cs.je.auth.config.jwt;
/**
* #author sawai
*
*/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.savedrequest.NullRequestCache;
import com.cs.je.auth.filter.CORSFilter;
#Configuration
#EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
#Value("${security.oauth2.resource.id}")
private String resourceId;
// The DefaultTokenServices bean provided at the AuthorizationConfig
#Autowired
private DefaultTokenServices tokenServices;
// The TokenStore bean provided at the AuthorizationConfig
#Autowired
private TokenStore tokenStore;
#Autowired
private CORSFilter corsFilter;
// To allow the rResourceServerConfigurerAdapter to understand the token,
// it must share the same characteristics with AuthorizationServerConfigurerAdapter.
// So, we must wire it up the beans in the ResourceServerSecurityConfigurer.
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId(resourceId)
.tokenServices(tokenServices)
.tokenStore(tokenStore);
}
public void configure(WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers("/api/candidate/**");
// webSecurity.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
// webSecurity.ignoring().antMatchers(HttpMethod.POST, "/oauth/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/").permitAll().and().authorizeRequests().antMatchers("/console/**")
.permitAll().and().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll().and()
// when restricting access to 'Roles' you must remove the "ROLE_" part role
// for "ROLE_USER" use only "USER"
//.antMatchers("/api/hello").access("hasAnyRole('USER')")
//.antMatchers("/api/admin").hasRole("ADMIN")
.authorizeRequests().antMatchers(HttpMethod.POST ,"/api/user/**").hasAnyAuthority("ROLE_SUPER_USER", "ROLE_ADMIN")
// restricting all access to /api/** to authenticated users
//.antMatchers("/**")
//.antMatchers("/api/**").authenticated();
.anyRequest().authenticated();
http.csrf().disable();
http.headers().frameOptions().disable();
http.requestCache().requestCache(new NullRequestCache());
http.httpBasic();
http.addFilterBefore(corsFilter, ChannelProcessingFilter.class);
}
}
All above config is on auth service side.
Now when user request for jwt token on auth, then we want to get access-token value on service layer, so i can call company service by secure url calling.
Please guide me how can we get access-token value when user request for jwt token. If you look in CustomTokenEnhancer then we tried to print access-token there by using following statements:
**System.out.println(accessToken.getRefreshToken());
System.out.println(accessToken.getRefreshToken().getValue());
System.out.println(accessToken.getTokenType());**
But values are similar to: 642e0cf2-9214-42d8-ae85-29e5cdfccef1
But we want actual token here.
Please guide me.
You can use that method
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String TOKEN_SEPERATOR = " ";
public static String getAccessToken(){
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
return Arrays.asList(request.getHeader(AUTHORIZATION_HEADER).split(TOKEN_SEPERATOR)).get(1);
}
return null;
}

Resources