9

I am new to Spring security and developing spring REST API with OAuth2 authentication and authorization. Everything is working fine, but when I request Refresh token, than I get error which states -

org.springframework.security.oauth2.provider.endpoint.TokenEndpoint handleException IllegalStateException, UserDetailsService is required.

NOTE: I am using custom authentication provider to authenticate user credentials from database.

Here is my code:

Web security configure adapter:

@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private ClientDetailsService clientDetailsService;

@Autowired
private CustomAuthenticationProvider authProvider;

@Override
protected void configure(
  AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authProvider);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .csrf().disable()
    .anonymous().disable()
    .authorizeRequests()
    .antMatchers("/oauth/token").permitAll();
}

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


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

@Bean
@Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
    TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
    handler.setTokenStore(tokenStore);
    handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
    handler.setClientDetailsService(clientDetailsService);
    return handler;
}

@Bean
@Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
    TokenApprovalStore store = new TokenApprovalStore();
    store.setTokenStore(tokenStore);
    return store;
}

}

Custom Authentication Provider

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

@Override
public Authentication authenticate(Authentication authentication) 
  throws AuthenticationException {

    String username = authentication.getName();
    String password = authentication.getCredentials().toString();
    Ss_admin_service ss_admin_service = new Ss_admin_service();
    Ss_admin ss_admin = new Ss_admin();
    ss_admin.setA_password(password);
    ss_admin.setA_username(username);
    ss_admin_service.doLogin(ss_admin);
    if(!ss_admin_service.doLogin(ss_admin)) {
        throw new BadCredentialsException("Invalid username/password");
    }
    return new UsernamePasswordAuthenticationToken(username, password, (Collection<? extends GrantedAuthority>) new ArrayList<>());
}

@Override
public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
}

}

Authorization Server Configuration

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

private static String REALM="MY_OAUTH_REALM";

@Autowired
private TokenStore tokenStore;

@Autowired
private UserApprovalHandler userApprovalHandler;

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

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

    clients.inMemory()
        .withClient("my-trusted-client")
        .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
        .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
        .scopes("read", "write", "trust")
        .secret("secret")
        .accessTokenValiditySeconds(10).//Access token is only valid for 10 sec for testing.
        refreshTokenValiditySeconds(10);//Refresh token is only valid for 10 sec for testing.
}

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

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer.realm(REALM+"/client");
}

}

Resource server config

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

private static final String RESOURCE_ID = "SPRING_REST_API";

@Override
public void configure(ResourceServerSecurityConfigurer resources) {
    resources.resourceId(RESOURCE_ID).stateless(false);
}

@Override
public void configure(HttpSecurity http) throws Exception {
    http
    .anonymous().disable()
    .requestMatchers().antMatchers("/admin/**")
    .and().authorizeRequests()
    .antMatchers("/admin/**").access("hasRole('ADMIN')")
    .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}

}
Zalak Parikh
  • 450
  • 4
  • 11

5 Answers5

8

Inject UserDetailsService in your AuthorizationServerConfigurerAdapter:

@Autowired
private UserDetailsService userDetailsService;

And then configure it on this configure method:

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    
    endpoints
    ...

    .userDetailsService(userDetailsService);
}
Nelio Alves
  • 1,231
  • 13
  • 34
  • If I create an `AuthorizationServerConfigurerAdapter` the API suddenly starts returning "Full authentication is required to access this resource", even though nothing else changed. – Haroldo_OK Feb 04 '21 at 18:25
  • If that happens to you, doing the injection on `WebSecurityConfigurerAdapter` as shown on https://stackoverflow.com/a/43907120/679240 should do the trick. – Haroldo_OK Feb 04 '21 at 18:36
1

I've solved this problem to use endpoints.tokenServices().

see also: Refresh token call fails using spring security an oauth2 with error: UserDetailsService is required

0

Can you try injecting ClientDetailsService to Authorization Server Config and configure it as below;

Authorization Server Configuration

.....

@Autowired
private ClientDetailsService clientDetailsService;

......
......

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(clientDetailsService);
}
Ali Akkaya
  • 51
  • 3
0

In my case, Nelio Alves' answer almost did the trick, but it started returning "Full authentication is required to access this resource" for every request. Instead, I had to do the injection on WebSecurityConfigurerAdapter, roughly based on Vijay Nandwana's answer to another similar question.

Here's my code, for reference:

public class MyWebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private AtsUserDetailsService userDetailsService;

    (...)

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        this.endpoints.getEndpointsConfigurer().userDetailsService(this.userDetailsService);
        
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.POST, "/oauth/token").permitAll()
            .anyRequest().fullyAuthenticated();
    }

    (...)

}
Haroldo_OK
  • 6,612
  • 3
  • 43
  • 80
0

Your scope and authorization_grant_types should be as following. This is how it solved for me.

enter image description here