Reputation: 21
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
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
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
}
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
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
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