can I include user information while issuing an access token? - spring

I have seen in some oauth2 implementations additional information on the response returned by the authorization server when it issues access tokens. I'm wondering if there is a way to accomplish this using spring-security-oauth2. I would love to be able to include some user authorities on the access token response so that my consuming applications don't need to manage the user authorities but can still set the user on their own security contexts and apply any of their own spring-security checks.
How would I get that information on the access token response?
How would I intercept that information on the oauth2 client side and set it on the security context?
I suppose another option would be to use JWT tokens and share the appropriate information with the client applications so that they can parse the user / authorities out of the token and set it on the context. This makes me more uncomfortable since I'd prefer to be in control of which client applications could have access to this information (trusted apps only) and AFAIK only the authorization server and resource server should know how to parse the JWT tokens.

You will need to implement a custom TokenEnhancer like so:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customInfo", "some_stuff_here");
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
and add it to your AuthorizationServerConfigurerAdapter as a bean with the corresponding setters
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
// Some autowired stuff here
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// #formatter:off
endpoints
// ...
.tokenEnhancer(tokenEnhancer());
// #formatter:on
}
#Bean
#Primary
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
// ...
tokenServices.setTokenEnhancer(tokenEnhancer());
return tokenServices;
}
// Some #Bean here like tokenStore
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
}
then in a controller (for example)
#RestController
public class MyController {
#Autowired
private AuthorizationServerTokenServices tokenServices;
#RequestMapping(value = "/getSomething", method = RequestMethod.GET)
public String getSection(OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = tokenServices.getAccessToken(authentication).getAdditionalInformation();
String customInfo = (String) additionalInfo.get("customInfo");
Collection<? extends GrantedAuthority> authorities = (Collection<? extends GrantedAuthority>) additionalInfo.get("authorities");
// Play with authorities
return customInfo;
}
}
I'm personnaly using a JDBC TokenStore so my "Some autowired stuff here" are corresponding to some #Autowired Datasource, PasswordEncoder and what not.
Hope this helped!

If you are using Spring's JwtAccessTokenConverter or DefaultAccessTokenConverter you can add your custom CustomTokenEnhancer (see first response) and apply it using a TokenEnhancerChain like this:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore())
.tokenEnhancer(enhancerChain)
.authenticationManager(authenticationManager);
}
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("my_signing_key");
return converter;
}
#Bean public TokenEnhancer customTokenEnhancer() {
return new CustomTokenEnhancer();
}
Another solution is to create a custom TokenConverter that extends Spring's JwtAccessTokenConverter and override the enhance() method with your custom claims.
public class CustomTokenConverter extends JwtAccessTokenConverter {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customized", "true");
User user = (User) authentication.getPrincipal();
additionalInfo.put("isAdmin", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()).contains("BASF_ADMIN"));
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return super.enhance(accessToken, authentication);
}
}
And then:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.tokenEnhancer(customTokenEnhancer())
.authenticationManager(authenticationManager);
}
#Bean public CustomTokenConverter customTokenEnhancer() {
return new CustomTokenConverter();
}

Together with:
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
You have to include
#Bean
public DefaultAccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
and add everything to endpoints config:
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.tokenEnhancer(tokenEnhancer())
.accessTokenConverter(accessTokenConverter())
.authorizationCodeServices(codeServices)
.authenticationManager(authenticationManager)
;
}
Without it, your CustomTokenEnhancer will not work.

package com.security;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.core.userdetails.User;
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.stereotype.Component;
#Component
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
// TODO Auto-generated method stub
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customInfo", "some_stuff_here");
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Following is the xml configuration:
<bean id="tokenEnhancer" class="com.security.CustomTokenEnhancer" />
<!-- Used to create token and and every thing about them except for their persistence that is reposibility of TokenStore (Given here is a default implementation) -->
<bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore" ref="tokenStore" />
<property name="accessTokenValiditySeconds" value="30000000"></property>
<property name="refreshTokenValiditySeconds" value="300000000"></property>
<property name="supportRefreshToken" value="true"></property>
<property name="clientDetailsService" ref="clientDetails"></property>
<property name="tokenEnhancer" ref="tokenEnhancer" />
</bean>
That's how I was able to add extra information to the Token.

create a class file CustomTokenEnhancer
#Component
public class CustomTokenConverter extends JwtAccessTokenConverter {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customized", "true");
User user = (User) authentication.getPrincipal();
additionalInfo.put("role", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return super.enhance(accessToken, authentication);
}
}
paste below written code in AuthorizationServerConfig
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(),accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(customTokenEnhancer())
.authenticationManager(authenticationManager);
}
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter= new JwtAccessTokenConverter();
converter.setSigningKey("my_signing_key");
return converter;
}
#Bean
public CustomTokenConverter customTokenEnhancer() {
return new CustomTokenConverter();
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
import appropriate libraries after paste the above codes
output response of Custom Token Enhancer..click here

I solve this problem when excluded UserDetailsServiceAutoConfiguration.
Like this. Maybe wiil be helpful in OAuth2 resource servers.
#SpringBootApplication(exclude = [UserDetailsServiceAutoConfiguration::class])
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}

Related

spring security Authentication and resource server seprated check_token is not sending authorization header (outh2 jwt spring boot, zuul)

problem
resource server making request for check_token its not passing authorization token which is implemented by spring security. how we can pass authorization token for /check_token endpoint?
I am using zuul as api gateway and all request go through zuul only.
i created auth server which is authentication server (spring cloud project)
and code is given below for authorization and i register authenticationManager with webSecurityConfigurationAdapter
code is given below
AuthorizationServerConfig.java
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Value("${config.oauth2.tokenTimeout}")
private int expiration;
#Value("${config.oauth2.privateKey}")
private String privateKey;
#Value("${config.oauth2.publicKey}")
private String publicKey;
#Autowired
private ClientDetailsService clientDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("client")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("client_credentials", "password", "refresh_token", "authorization_code")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(expiration)
.refreshTokenValiditySeconds(expiration);
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
return converter;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Value("${filters.cors.allowed.origin}")
private String allowedOriginUrlForCordFilter;
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(allowedOriginUrlForCordFilter);
//config.addAllowedOrigin("http://localhost:8080/");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setClientDetailsService(clientDetailsService);
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
/**
* Defines the authorization and token endpoints and the token services
* #param endpoints
* #throws Exception
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.tokenStore(tokenStore())
.tokenServices(tokenServices())
.accessTokenConverter(accessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
}
resource server another spring cloud project which is basically our business services
and for further communication i need authorization token this code is implement by using filter i implemented.
ResourceServerConfiguration.java
#Configuration
#EnableResourceServer
#EnableWebSecurity(debug = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// private static final Logger LOGGER =
// Logger.getLogger(ResourceServerConfiguration.class);
#Value("${config.oauth2.publicKey}")
private String publicKey;
#Value("${config.oauth2.privateKey}")
private String privateKey;
#Value("${config.oauth2.resource.id}")
private String resourceId;
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().anonymous().disable().authorizeRequests()
// .antMatchers(HttpMethod.OPTIONS).permitAll()
// .antMatchers("/oauth/**").authenticated()
.antMatchers("/register/**").authenticated();
}
// #Override
// public void configure(ResourceServerSecurityConfigurer resources) {
// resources.resourceId(resourceId).tokenServices(tokenServices()).tokenStore(tokenStore());
// }
// #Bean
// #Primary
// public DefaultTokenServices tokenServices() {
// DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
// defaultTokenServices.setTokenStore(tokenStore());
// defaultTokenServices.setSupportRefreshToken(true);
// defaultTokenServices.setTokenEnhancer(accessTokenConverter());
// return defaultTokenServices;
// }
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
return converter;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Primary
#Bean
public RemoteTokenServices tokenServices() {
final RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("http://localhost:8765/auth/oauth/check_token/");
tokenService.setClientId("client");
tokenService.setClientSecret("secret");
//tokenService.setTokenName("");
// tokenService.setTokenStore(tokenStore());
// tokenService.setSupportRefreshToken(true);
tokenService.setAccessTokenConverter(accessTokenConverter());
return tokenService;
}
}
now issue is when resource server making request for check_token there i am anable to pass authorization token.

Load JWT signature key from a dynamic base

I deployed an Authorization Server using AuthorizationServerConfigurerAdapter and the users and clients are configured from the implementation of the UserDetailsService and ClientDetailsService services that gather the required information in the database.
#Configuration
#EnableAuthorizationServer
public class OAuth2JwtAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AppClientDetailsService clientDetailsService;
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.reuseRefreshTokens(false)
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The Authorization Server is working properly, but I would like the signature key set in the accessTokenConverter () method to be loaded dynamically, when I receive the request for a new Token, I would access the database and modify the Signing Key at this point and return the JWT Token with this new modified signature, it is currently only being configured the moment the application is started.
You can define an #Autowired JwtAccessTokenConverter property and modify its key at any point
#Autowired
public JwtAccessTokenConverter tokenConverter;
public void setSigningKey(String key) {
tokenConverter.setSigningKey(key);
}

Extract Currently Logged in User information from JWT token using Spring Security

I have implemented JWT and LDAP Authentication using Spring Security Oauth2. It seems to be working fine and I can login with my LDAP credentials.
Now, there is one requirement that I need to use the currently logged in user info to save details in database - specifically like when that user add/update a new record. I tried to get that using Spring security way using
SecurityContextHolder.getContext().getAuthentication().getDetails()
but it doesn't return all that information which I have in JWT. It just returns Remote IP,the JWT token value and authenticated true. It doesn't even return name().
I am new to JWT, so not sure if I need to extract it by reading that token and even how we can achieve it.
Any pointers will be appreciated.
Thanks.
The first thing you need to do is store the user information inside the JWT when it is created, then you have to extract it when it is used. I had a similar situation and I solved it by extending both the TokenEnhancer and JwtAccessTokenConverter.
I use the TokenEnhancer to embed my extended principal of type CustomUserDetailsinside the JWT additional information.
public class CustomAccessTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication != null) {
Object principal = authentication.getUserAuthentication().getPrincipal();
if (principal instanceof CustomUserDetails) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("userDetails", principal);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
}
}
return accessToken;
}
}
And then manually extract the extended principal when building the Authentication object when processing an authenticated request.
public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication != null) {
LinkedHashMap userDetails = (LinkedHashMap) map.get("userDetails");
if (userDetails != null) {
// build your principal here
String localUserTableField = (String) userDetails.get("localUserTableField");
CustomUserDetails extendedPrincipal = new CustomUserDetails(localUserTableField);
Collection<? extends GrantedAuthority> authorities = userAuthentication.getAuthorities();
userAuthentication = new UsernamePasswordAuthenticationToken(extendedPrincipal,
userAuthentication.getCredentials(), authorities);
}
}
return new OAuth2Authentication(authentication.getOAuth2Request(), userAuthentication);
}
}
and the AuthorizationServer configuration to tie it all together.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private DataSource dataSource;
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
CustomJwtAccessTokenConverter accessTokenConverter = new CustomJwtAccessTokenConverter();
accessTokenConverter.setSigningKey("a1b2c3d4e5f6g");
return accessTokenConverter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomAccessTokenEnhancer();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).passwordEncoder(passwordEncoder());
}
#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)
.userDetailsService(userDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(passwordEncoder());
security.checkTokenAccess("isAuthenticated()");
}
}
I am then able to access my extended principal in my resource controller like this
#RestController
public class SomeResourceController {
#RequestMapping("/some-resource")
public ResponseEntity<?> someResource(Authentication authentication) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return ResponseEntity.ok("woo hoo!");
}
}
Hope this helps!
In your REST Service, add the OAuth2Authentication Class as an argument
#RequestMapping(value = "/{id}/products", method = RequestMethod.POST)
public ResourceResponse<String> processProducts(OAuth2Authentication auth) {
Springboot will automatically map the logged-in user details to this object. Now, you can do the following to access the username
auth.getPrincipal().toString()

How to reuse oauth2 token from user (authorization_code) in a Rest Template

I have 3 applications
Frontend application
OAuth2 authentication server
REST api (RepositoryRestResources)
My users have to log in before being able to use the frontend application. This happens through SSO. They receive a token which is validated by the client before being let in.
I would like to reuse this token to make api requests. My REST api application is secured with the same SSO login (it is a resource for the frontend client) but I have no clue how to "add the authorization header" to be used in the RestTemplate I use for api requests.
I create my restTemplate like this:
public static RestTemplate build()
{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new Jackson2HalModule());
mapper.registerModule(new JavaTimeModule());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
converter.setObjectMapper(mapper);
return new RestTemplate(Arrays.asList(converter));
}
my resource server configuration:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter
{
#Value("${resource.id}")
private String resourceId;
#Override
public void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception
{
resources.resourceId(resourceId);
}
#Bean
public static TokenEnhancer tokenEnhancer()
{
return new JwtTokenEnhancer();
}
#Bean
public static JwtAccessTokenConverter accessTokenConverter()
{
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "somesecret".toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("pair"));
return converter;
}
#Bean
public static TokenStore tokenStore()
{
return new JwtTokenStore(accessTokenConverter());
}
}
I fixed it using an intercepter an manually adding the Token from the security context.
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new OAuthInterceptor());
In which the intercepter is defined as:
public class OAuthInterceptor implements ClientHttpRequestInterceptor
{
#Autowired
private AuthenticationHolder holder;
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
{
if (holder.getToken() == null)
{
//throw new IOException("Token not set");
System.out.println("##################### Token not set! ###################");
}
else
{
System.out.println("##################### Token found: " + holder.getToken());
HttpHeaders headers = request.getHeaders();
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + holder.getToken());
}
return execution.execute(request, body);
}
}
I use an interface which I implement in my client app:
public interface AuthenticationHolder
{
String getToken();
}
#Bean
public AuthenticationHolder getAuthenticationHolder()
{
return () ->
{
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails)
{
return ((OAuth2AuthenticationDetails) authentication.getDetails()).getTokenValue();
}
return null;
};
}
You can use the #PreAuthorize(ROLE) on the top of your methods, so when this method is called he will as for the Token then he will check if the provided Token has the necessary ROLE to use the method.
Of course that you will need to configure your API to connect to your OAuth Database.
Example:
#PreAuthorize("ROLE_ADMIN")
public void deleteAll(){
...
}

Spring oauth2 refresh token - Cannot convert access token to JSON

I'm trying to use a refresh token in a Spring OAuth application without success. The system will issue a refresh token on a password grant:
{
"access_token": "xxxxx",
"token_type": "bearer",
"refresh_token": "xxxxxx",
"expires_in": 21599,
"scope": "read write"
}
But trying to use the refresh token results in the following error:
curl -u acme -d "grant_type=refresh_token&refresh_token=xxxxxx" http://localhost:9999/uaa/oauth/token
{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}
My auth server config is as follows:
#Controller
#SessionAttributes("authorizationRequest")
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
#EnableResourceServer
#ImportResource("classpath:/spring/application-context.xml")
#Configuration
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
#RequestMapping("/user")
#ResponseBody
public Principal user(Principal user) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.toString());
return user;
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
#Configuration
#Order(-20)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// #formatter:off
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
// #formatter:on
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
#Configuration
public static class JwtConfiguration {
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
#Bean
public JwtTokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
#Configuration
#EnableAuthorizationServer
protected static class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter implements
EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private JwtTokenStore jwtTokenStore;
#Autowired
#Qualifier("myUserDetailsService")
private UserDetailsService userDetailsService;
#Autowired
private DataSource dataSource;
#Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setAuthenticationManager(authenticationManager);
tokenServices.setTokenEnhancer(tokenEnhancer());
return tokenServices;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
// The order is important here - the custom enhancer must come before the jwtAccessTokenConverter.
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter));
endpoints
.authenticationManager(authenticationManager)
.tokenEnhancer(tokenEnhancerChain)
.tokenStore(jwtTokenStore)
.userDetailsService(userDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
/*.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.autoApprove(true)
.authorities(ClientAuthoritiesConstants.CLIENT)
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer
.class, 1800));*/
}
}
/**
* Configures the global LDAP authentication
*/
#Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter implements EnvironmentAware {
private static final String ENV_LDAP = "authentication.ldap.";
private static final String PROP_SEARCH_BASE = "userSearchBase";
private static final String PROP_SEARCH_FILTER = "userSearchFilter";
private static final String PROP_GROUP_SEARCH_FILTER = "groupSearchFilter";
private static final String PROP_LDAP_URL = "url";
private static final String PROP_LDAP_USER = "userDn";
private static final String PROP_LDAP_PASS = "password";
private RelaxedPropertyResolver propertyResolver;
/**
* Maps the LDAP user to the Principle that we'll be using in the app
*/
public UserDetailsContextMapper userDetailsContextMapper() {
return new UserDetailsContextMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
// Get the common name of the user
String commonName = ctx.getStringAttribute("cn");
// Get the users email address
String email = ctx.getStringAttribute("mail");
// Get the domino user UNID
String uId = ctx.getStringAttribute("uid");
return new CustomUserDetails(email, "", commonName, authorities);
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
throw new IllegalStateException("Only retrieving data from LDAP is currently supported");
}
};
}
#Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_LDAP);
}
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchBase(propertyResolver.getProperty(PROP_SEARCH_BASE))
.groupSearchBase(propertyResolver.getProperty(PROP_SEARCH_BASE))
.userSearchFilter(propertyResolver.getProperty(PROP_SEARCH_FILTER))
.groupSearchFilter(propertyResolver.getProperty(PROP_GROUP_SEARCH_FILTER))
.userDetailsContextMapper(userDetailsContextMapper())
.contextSource()
.url(propertyResolver.getProperty(PROP_LDAP_URL))
.managerDn(propertyResolver.getProperty(PROP_LDAP_USER))
.managerPassword(propertyResolver.getProperty(PROP_LDAP_PASS));
}
}
}
Anyone have any ideas why the auth server isn't issuing a new token when given a valid refresh token?
had this issue. i was sending the "Bearer xxxxxx..." and the TokenEnhancer was expecting just "xxxxx..." without the "Bearer " prefix
I had the same issue. After some debugging it turned out my signature did not match.
In my case i set-up keys a bit differently, and there is a bug where the signing and verifying key miss-match.
https://github.com/spring-projects/spring-security-oauth/issues/1144
Also has same issue with Spring Boot 1.5.4
It is really actual that jwtAccessTokenConverter.setVerifierKey(publicKey);doesn't really set verifier(in debug value is null) that is used in -
JwtAccessTokenConverter
...protected Map<String, Object> decode(String token) {
try {
Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
as workaround helped:
private JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new CustomTokenEnhancer();
jwtAccessTokenConverter.setSigningKey(jwtSigningKey);
jwtAccessTokenConverter.setVerifier(new RsaVerifier(jwtPublicKey));
log.info("Set JWT signing key to: {}", jwtAccessTokenConverter.getKey());
return jwtAccessTokenConverter;
}
It is been two years I don't if it helps anyone but my same issue was due to I was not using the tokenEnhancer I used in my JwtTokenStore in my token service provider DefaultTokenServices.
<!-- Access token converter -->
<bean id="jwtAccessTokenConverter"
class="org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter">
<property name="signingKey" value="${security.jwt.signing-key}"/>
</bean>
<!-- Token store -->
<bean id="jwtTokenStore"
class="org.springframework.security.oauth2.provider.token.store.JwtTokenStore">
<constructor-arg name="jwtTokenEnhancer" ref="jwtAccessTokenConverter"/>
</bean>
<!-- Creates token store services provider -->
<bean id="tokenServiceProvider"
class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore"
ref="jwtTokenStore"/>
<!--This must be set according to z docs -->
<property name="tokenEnhancer"
ref="jwtAccessTokenConverter"/>
<property name="supportRefreshToken"
value="true"/>
<property name="accessTokenValiditySeconds"
value="${security.jwt.access-token-validity-seconds}"/>
<property name="refreshTokenValiditySeconds"
value="${security.jwt.refresh-token-validity-seconds}"/>
</bean>
I had the same issue with Spring Boot 2.5.7. Because I missed set verifier for JwtAccessTokenConverter.
My solution:
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new CustomJwtAccessTokenConverter(privateKey);
jwtAccessTokenConverter.setVerifier(new RsaVerifier(publicKey));
return jwtAccessTokenConverter;
}
So it looks like the issue was an invalid refresh_token format. Due to my config, what the auth server was expecting was a valid JWT, whereas I was sending it a plain bearer token. Hence the error message 'cannot convert token to JSON'.
Incidentally, I found this document useful in understanding how all the parts of Spring OAuth fit together, which led me to figuring out what was going on here:
https://github.com/spring-projects/spring-security-oauth/blob/master/docs/oauth2.md

Resources