1

I am trying to implement oauth2 with a jwt in spring boot and the autentication works but when I want to get the refresh_token an error occurs that indicates the following ...

java.lang.IllegalStateException: UserDetailsService is required. at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:464) at org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper.loadUserDetails(UserDetailsByNameServiceWrapper.java:68) at org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider.authenticate(PreAuthenticatedAuthenticationProvider.java:103) at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175) at org.springframework.security.oauth2.provider.token.DefaultTokenServices.refreshAccessToken(DefaultTokenServices.java:150)

What am I doing wrong?

These are my files

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

@Value("${token.secret}")
private String secret;
@Value("${server.servlet.context-path}")
private String contextPath;
@Value("${oauth2.client.id}")
public String  CLIENT_ID;
@Value("${oauth2.client.secret}")
public String  CLIENT_SECRET;
@Value("${oauth2.scope.read}")
public String  SCOPE_READ;
@Value("${oauth2.grant.types}")
public String  GRANT_TYPES;
@Value("${oauth2.scopes}")
public String  SCOPES;
@Value("${oauth2.access.token.validity}")
public Integer  TOKEN_VALID_SECONDS;
@Value("${oauth2.refresh.token.validity}")
public Integer  REFRESH_TOKEN_VALID_SECONDS;


@Autowired
private DataSource dataSource;

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

@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}

@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { 
    clients.inMemory()
            .withClient(CLIENT_ID)
            .secret(passwordEncoder().encode(CLIENT_SECRET))
            .authorizedGrantTypes("password", "refresh_token", "client_credentials","authorization_code")
            .scopes("read", "write", "trust")
            .accessTokenValiditySeconds(TOKEN_VALID_SECONDS)
            .refreshTokenValiditySeconds(REFRESH_TOKEN_VALID_SECONDS);
} 

@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()));
    endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);

}

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


@Bean
public TokenEnhancer tokenEnhancer() {
    return new CustomTokenEnhancer();
}

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

And this is my SecurityConfig class

@EnableGlobalMethodSecurity(securedEnabled=true)
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private final CustomUserDetailsService customUserDetailsService;
@Autowired
public BCryptPasswordEncoder passwordEncoder;

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

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

@Override
public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/oauth/**").permitAll()
    .antMatchers("/oauth/token/**").permitAll()
    .antMatchers("/api/**" ).authenticated()
    .anyRequest().authenticated()
    .and().formLogin().permitAll()
    .and().csrf().disable();

}

}

public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private UserService userService;
//login web service url describe on properties file
@Value("${loginServiceUri}")
public String loginServiceUri;
//login web service enabled 
@Value("${loginWebServiceEnabled}")
public Boolean loginWebServiceEnabled;


@Override
public UserDetails loadUserByUsername(String username) {

    log.debug("Trying to authenticate user with details....");

    if (loginWebServiceEnabled && loginServiceUri != null){
        //its necessary to call external Web Service to find the user and then look for 
        //into database
        UserDto userDto = this.loginWebService(username);

        if (userDto != null) {
            // look for the user in data base
            log.debug("User found at external login web service trying to look for at data base");
            return lookUserDataBase(userDto.getUsername());
        } else {
            log.error("User not found at external login web service", username);
            throw new UsernameNotFoundException(username);
        }

    } else {
        // look for the user in data base
        return lookUserDataBase(username);
    }


}

/**
 * Look for use in data base by user name

 * @return
 */
private UserDetails lookUserDataBase(String userName) {
    UserEntity user = userService.findEntityByUsername(userName);
    if (user == null) {
        log.error("User not found in data base", userName);
        throw new UsernameNotFoundException(userName);
    }
    log.debug("User found in data base with userName: " + userName + " and authorities: " + user.getUserAuthority().toString());
    return new UserAuthenticatedDetails(user);
}


/**
 *  Example Login Web Service call login 
 * @param name
 * @return
 */
private UserDto loginWebService (String name){
    xxxxxxxxxxx
}

}

Mickey
  • 173
  • 2
  • 11

3 Answers3

3

Add these in your WebSecurityConfigurerAdapter class

@Autowired
public void setApplicationContext(ApplicationContext context) {
    super.setApplicationContext(context);
    AuthenticationManagerBuilder globalAuthBuilder = context
            .getBean(AuthenticationManagerBuilder.class);
    try {
        globalAuthBuilder.userDetailsService(userDetailsService);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

There is a "local" AuthenticationManagerBuilder and a "global" AuthenticationManagerBuilder and we need to set it on the global version in order to have this information passed to these other builder contexts.

Read more about it here

MyTwoCents
  • 7,284
  • 3
  • 24
  • 52
  • Thanks! It works. I solved it with other solution but this is valid too! (i put my solution in the posth)+ – Mickey Feb 25 '20 at 01:18
1

I solved with this lines in SecurityConfig;

 public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
    endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);

    //this line solved the problem
    endpoints.userDetailsService(customUserDetailsService);**


}
Mickey
  • 173
  • 2
  • 11
0

Either you configure some inMemory user as follows:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
            .withUser("user")
                .password("password")
                .roles("USER")
        .and()
            .withUser("manager")
                .password("password")
                .credentialsExpired(true)
                .accountExpired(true)
                .accountLocked(true)
                .authorities("WRITE_PRIVILEGES", "READ_PRIVILEGES")
                .roles("MANAGER");
}

or you implement a UserDetailService e.g. which looks up in the database for a user.

Weso
  • 408
  • 4
  • 14
  • I do my custom userdetailservice which search the user in databes --> securityConfig class ¿is it correct? protected void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService) .passwordEncoder(passwordEncoder); } – Mickey Feb 25 '20 at 00:15
  • True... Mmh... And the customUserDetailsService is not null? – Weso Feb 25 '20 at 00:22
  • No, it is not null – Mickey Feb 25 '20 at 00:38