11

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?

TimS
  • 733
  • 3
  • 10
  • 19

6 Answers6

13

had this issue. i was sending the "Bearer xxxxxx..." and the TokenEnhancer was expecting just "xxxxx..." without the "Bearer " prefix

Alberto
  • 301
  • 2
  • 9
  • 1
    This, in the `Authorization` header ? Like `Authorization=[9a1b74f0-9dea-4506-ae5e-0e2e824822b9]` ? – Stephane Jan 09 '19 at 10:41
  • 1
    Without key pair jks config, It work and I need Bearer, I don't think you should remove it. I have tried and that does give an authorization error. – Dimitri Kopriwa Nov 22 '19 at 13:12
6

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

Niklas Lönn
  • 201
  • 3
  • 3
2

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;
    }
Oleksandr Yefymov
  • 6,081
  • 2
  • 22
  • 32
1

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>
1

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;
}
yuen26
  • 871
  • 11
  • 12
-1

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

TimS
  • 733
  • 3
  • 10
  • 19
  • 13
    It would have definitely helped if you have posted your solution rather than just giving a specific link – KyelJmD Apr 09 '17 at 12:33
  • @TimS Can you please add what we have to do to solve this issue. I couldn't figure it out. – Ibrahim Aug 11 '17 at 19:51
  • 3
    @Ibrahim You'll need to send it a valid JWT to solve the issue. Unfortunately there are a myriad of different reasons why your token would be invalid, and they depend on your config. In my case, my bearer token wasn't encoded properly due to me sending it in the wrong format. Either way, I'd recommend reading the link in the answer as it sets out what each Spring OAuth 2.0 component does, and how they fit together. If you understand the detail of the Spring implementation, solving these issues gets a lot easier. – TimS Nov 16 '17 at 21:15
  • this is not a "solution" just a reference – jpganz18 Jun 08 '19 at 10:36
  • I have the same issue, can you help? – Dimitri Kopriwa Nov 22 '19 at 13:09
  • 9
    Link is broken. – Peter Black Feb 04 '20 at 22:11
  • Updated link: https://github.com/Diffblue-benchmarks/Spring-projects-spring-security-oauth/blob/master/docs/oauth2.md – peceps Feb 03 '22 at 09:31