afcrowther
afcrowther

Reputation: 21

Spring Boot Oauth2 Refresh Token - IllegalStateException

So, I can get an access token all good with a standard CURL, but as soon as I try to get an access token the application throws an 'IllegalStateException - UserDetailsService Required'. As far as I knew we didnt need to authenticate user details again once we have refresh token? But anyway it should be there anyway considering it has to authenticate for the access token the first time around.

See below my Oauth2 config:

@Configuration
public class OAuth2Config {

@Configuration
@EnableResourceServer
protected static class ResourceServiceConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/oauth/token/").permitAll()
            .antMatchers(HttpMethod.GET, "/api/**").access("#oauth2.hasScope('read')")
            .antMatchers(HttpMethod.OPTIONS, "/api/**").access("#oauth2.hasScope('read')")
            .antMatchers(HttpMethod.POST, "/api/**").access("#oauth2.hasScope('write')")
            .antMatchers(HttpMethod.PUT, "/api/v1/**").access("#oauth2.hasScope('write')")
            .antMatchers(HttpMethod.PATCH, "/api/**").access("#oauth2.hasScope('write')")
            .antMatchers(HttpMethod.DELETE, "/api/**").access("#oauth2.hasScope('write')");
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
            .resourceId(<RESOURCE ID>)
            .tokenStore(tokenStore);
    }
}

@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Value("${oauth.clientid}")
    private String CLIENT_ID;

    @Value("${oauth.clientsecret}")
    private String CLIENT_SECRET;

    @Value("${oauth.tokenvalidity}")
    private String VALIDITY_SECONDS;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .tokenStore(tokenStore())
            .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
            .withClient(CLIENT_ID)
            .scopes("read", "write")
            .authorities(Authorities.USER)
            .authorizedGrantTypes("password", "refresh_token")
            .secret(CLIENT_SECRET)
            .accessTokenValiditySeconds(Integer.parseInt(VALIDITY_SECONDS));
    }

}
}

And Web Security Config :

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private CustomUserDetailsService userDetailsService;

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder());
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

@Override
public void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests().anyRequest().permitAll();
}

}

So Yeah this one doesn't seem to make sense because 1. why is it even going for a user details service when we are attempting a refresh token grant? and 2. why can it not find the user details service anyway when it is clearly there and working for the password grant beforehand?

Thanks

Upvotes: 0

Views: 2971

Answers (3)

chendu
chendu

Reputation: 584

I'm using spring boot + OAuth2, and meet the same exception
IllegalStateException - UserDetailsService Required.

And I'm testing with in memory authentication manager. I resolved as below

  1. WebSecurityConfig(extends WebSecurityConfigurerAdapter) - declear default bean UserDetailsService

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        @Bean("userDetailsService")
        public UserDetailsService userDetailsServiceBean() throws Exception {
            return super.userDetailsServiceBean(); //default
        }
    
        // other logics
    
    }
    
  2. OAuth2ServerConfig(extends AuthorizationServerConfigurerAdapter) - inject UserDetailsService bean into oAuth2 config

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        @Qualifier("userDetailsService")
        private UserDetailsService userDetailsService;
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .authenticationManager(authenticationManager)
            .tokenStore(tokenStore)
            .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
            .userDetailsService(userDetailsService); //inject here
        }
    
        // other logics
    
    }
    

Upvotes: 4

Alex
Alex

Reputation: 378

In your AuthorizationServerConfig class:

@Autowired
private CustomUserDetailsService userDetailsService;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints
        .authenticationManager(authenticationManager)
        .tokenStore(tokenStore())
        .userDetailsService(userDetailsService);
}

Upvotes: 0

afcrowther
afcrowther

Reputation: 21

Found the answer here: https://github.com/spring-projects/spring-security-oauth/blob/master/tests/annotation/jdbc/src/main/java/demo/Application.java#L136

I added this as another inner class to my Oauth config class, and removed any authentication manager / password encoder config from the Web Security Config class, literally the only thing left in there at the moment is the @Override on the configure(HttpSecurity http) function.

@Configuration
@Order(Ordered.LOWEST_PRECEDENCE - 20)
protected static class AuthenticationManagerConfiguration extends GlobalAuthenticationConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }

}

As the author of the solution comments "Global authentication configuration ordered after the one in Spring Boot (so the settings here overwrite the ones in Boot)." Although he also mentions this won't be an issue in spring boot 1.2.3 anyway, but for now this is what I needed to get it to work and find my UserDetailsService.

Upvotes: 0

Related Questions