Issue
Following the tutorial that can be found here to replace existing oauth configuration that returns an 'access token' for a a jwt token. When I run the application and query the server for authentication, it seem to be returning an "access_token" instead of a JWT Token. The tutorial is using spring boot and our application is a non-boot plain spring mvc, therefore not sure if there is any additional steps involved?
Server response:
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ3aWxsbGFkaXNsYXciLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjQ2OGI3MzFmLTUxMzgtNDZhYi04MTU3LTU1MmZlMjM1MzY2ZSIsImNsaWVudF9pZCI6ImNsaWVudGFwcCIsInNjb3BlIjpbInJlYWRfd3JpdGUiXSwib3JnYW5pemF0aW9uIjoid2lsbGxhZGlzbGF3QmdNSiJ9.fUhFeUDuhm8f2V7CuURsZWKoAKjNZixk5rUa0Jyzov8","refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbsUiOiJ3aWxsbGFkaXNsYXciLCJzY29wZSI6WyJyZWFkX3dyaXRlIl0sIm9yZ2FuaXphdGlvbiI6IndpbGxsYWRpc2xhd0JnTUoiLCJhdGkiOiI0NjhiNzMxZi01MTM4LTQ2YWItODE1Ny01NTJmZTIzNTM2NmUiLCJleHAiOjE1ODM4NDA5NTgsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiOGIxNGE3NjMtZmMwMy00MDQ4LWJkNGQtYjZiMTUyOGU2NTE4IiwiY2xpZW50X2lkIjoiY2xpZW50YXBwIn0.UhkxVsgM4CnZeRRKGyyCbiyqb2M0BmL56sHbsxt5Opk","idToken":null,"tokenEndpoint":"http://localhost:8080/oauth/token","scopes":["read_write"],"expiration":null}
//OAuth Server
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {
final String clientId;
final String clientSecret;
final String redirectUri;
final String grantType;
final String scope;
....
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
DataSource dataSource;
@Autowired
RepositoryUserDetailsService userDetailsService;
@Autowired
private CustomAccessTokenConverter customAccessTokenConverter;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager);
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(passwordEncoder);
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
@Bean
@Primary
public ResourceServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.
jdbc(dataSource)
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.autoApprove(true)
.redirectUris(redirectUri)
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(0)
.scopes(scope);
}
}
//Resource Server
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAccessTokenConverter customAccessTokenConverter;
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/register/**").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin().loginPage("/signin/**")
.and()
.requestMatchers()
.antMatchers("/api/**");
}
@Override
public void configure(ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(customAccessTokenConverter);
converter.setSigningKey("123");
return converter;
}
@Bean
@Primary
public ResourceServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
}
//CustomTokenEnhancer Class
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(
OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put(
"organization", authentication.getName() + randomAlphabetic(4));
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(
additionalInfo);
return accessToken;
}
}
//CustomAccessTokenConverter Class
@Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication authentication =
super.extractAuthentication(claims);
authentication.setDetails(claims);
return authentication;
}
}
Solution
Your Spring Security OAuth2 configuration works as it should be and is already returning a JWT token.
Following is the payload it returns
eyJ1c2VyX25hbWUiOiJ3aWxsbGFkaXNsYXciLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjQ2OGI3MzFmLTUxMzgtNDZhYi04MTU3LTU1MmZlMjM1MzY2ZSIsImNsaWVudF9pZCI6ImNsaWVudGFwcCIsInNjb3BlIjpbInJlYWRfd3JpdGUiXSwib3JnYW5pemF0aW9uIjoid2lsbGxhZGlzbGF3QmdNSiJ9
This is what it looks like after decoding it with base64
{"user_name":"willladislaw","authorities":["ROLE_ADMIN"],"jti":"468b731f-5138-46ab-8157-552fe235366e","client_id":"clientapp","scope":["read_write"],"organization":"willladislawBgMJ"}
You can now use the following JWT Token in the header value Authorization: Bearer <JWT Token>
and access any protected resource.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ3aWxsbGFkaXNsYXciLCJhdXRob3JpdGllcyI6WyJST0xFX0FETUlOIl0sImp0aSI6IjQ2OGI3MzFmLTUxMzgtNDZhYi04MTU3LTU1MmZlMjM1MzY2ZSIsImNsaWVudF9pZCI6ImNsaWVudGFwcCIsInNjb3BlIjpbInJlYWRfd3JpdGUiXSwib3JnYW5pemF0aW9uIjoid2lsbGxhZGlzbGF3QmdNSiJ9.fUhFeUDuhm8f2V7CuURsZWKoAKjNZixk5rUa0Jyzov8
What you have done is you have asked Spring Security OAuth2 to generate a JWT Token with self contained information about the logged in user and replace that for the standard UUID based accessToken
it uses by default. Just because you are using JWT Token doesn't mean you will get a jwtToken return from oauth/token
endpoint at login. Learn more about JWT.
Answered By - shazin
Answer Checked By - David Marino (JavaFixing Volunteer)