简体   繁体   中英

Spring Security 5 - My custom UserDetailsService is not called

I have found several questions about similar problems but none of them is exactly my case.

I am using the following class to configure the authentication/authorization in my application (a REST API).

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .csrf().disable()
            .authorizeRequests()
            .and()
            .exceptionHandling(e -> e
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
            )
            .antMatcher("/application-api/v1/**").authorizeRequests()
            .antMatchers("/application-api/v1/non-protected").permitAll()
            .anyRequest()
                .authenticated()
            .and()
            .oauth2ResourceServer()
            .opaqueToken();
    }

    @Override
    protected UserDetailsService userDetailsService() {
        return new ApplicationUserDetailsService();
    }
}

No matter what I try, my custom ApplicationUserDetailsService is never used. Can anyone see what is wrong with this configuration?

I have also tried to specify the following bean:

@Service
public class ApplicationUserDetailsService implements UserDetailsService {
    private static final Logger logger = LoggerFactory.getLogger(ApplicationUserDetailsService.class);
    public ApplicationUserDetailsService() {
        logger.debug("Creating instance of ApplicationUserDetailsService");
    }
    @Override
    public UserDetails loadUserByUsername(String userName) {
        logger.debug("Creating user details for {}", userName);

        return new ApplicationUser(userName);
    }
}

It did not work either.

########### UPDATE ###########

I have modified my SecurityConfiguration as follows:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

    @Qualifier("applicationUserDetailsService")
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        logger.info("Oauth2 enabled: {}", enabled);
        httpSecurity.sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .csrf().disable()
            .exceptionHandling(e -> e
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
            )
            .antMatcher("/application-api/v1/**").authorizeRequests()
            .antMatchers("/", "/application-api/unprotected").permitAll()
            .anyRequest()
                .authenticated()
            .and()
            .oauth2ResourceServer()
            .opaqueToken();
    }

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

    @Bean(name="passwordEncoder")
    public PasswordEncoder passwordencoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected UserDetailsService userDetailsService() {
        return userDetailsService;
    }
}

I can see that my instance is used in the configuration, but nevertheless it is never called.

The cause probably is because you are constructing the ApplicationUserDetailsService using the new keyword that makes it non-spring dependency

you have 2 options either make the method return the UserDetailsService public with marking it with @Bean

@Bean
public UserDetailsService userDetailsService() {
    return new ApplicationUserDetailsService();
}

OR

Inject ApplicationUserDetailsService in SecurityConfiguration via @Autowired or constructor injection and in configure method set the security service in the HttpSecurity object

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
    private ApplicationUserDetailsService applicationUserDetailsService;
    // inject via constructor
    public SecurityConfig(ApplicationUserDetailsService applicationUserDetailsService) {
        this.applicationUserDetailsService = applicationUserDetailsService;

    }    

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception 
    {
       httpSecurity.userDetailsService(applicationUserDetailsService);
       .... rest of security config code
    }

This could be a case of bean creation order. It could be that at the time of creation of SecurityConfiguration the applicationUserDetailsService bean is not available to be Autowired. You can try to add @dependson on SecurityConfiguration and see if that works.

You are injecting UserDetailService inside your configuration. There is UserDetailsServiceAutoConfiguration which is by default from Spring.

@Autowired
private UserDetailsService userDetailsService;

Instead of userDetailsService, Autowire ApplicationUserDetailsService in SecurityConfiguration. Example:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

//This should work. If not Found it will give error. Might be the case  where it is injecting Other UserDetailsService.
@Autowired
private ApplicationUserDetailsService userDetailsService;

@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
    logger.info("Oauth2 enabled: {}", enabled);
    httpSecurity.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .csrf().disable()
        .exceptionHandling(e -> e
            .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
        )
        .antMatcher("/application-api/v1/**").authorizeRequests()
        .antMatchers("/", "/application-api/unprotected").permitAll()
        .anyRequest()
            .authenticated()
        .and()
        .oauth2ResourceServer()
        .opaqueToken();
}

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

@Bean(name="passwordEncoder")
public PasswordEncoder passwordencoder(){
    return new BCryptPasswordEncoder();
}

}

The problem is not with the injection of the bean. Given that you are seeing the expected bean injected, the reason is probably that the service is not used.

You need to check what authentication provider you are using. With the default providers, the service is used only if you your authentication provider is based on username and password.

See for example the Spring documentation:

https://docs.spring.io/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/core/userdetails/UserDetailsService.html

The following article describes how to customise the user details.

https://docs.spring.io/spring-security/site/docs/5.2.x/reference/html/oauth2.html#oauth2login-advanced-oauth2-user-service

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM