首页 > 解决方案 > 创建并使用 loadUserByEmail 而不是 loadUserByUsername

问题描述

我正在寻找一种方法来创建和使用我自己的方法在 Java Spring Security 中加载用户。

我想不是通过用户名而是通过电子邮件来检索我的用户。

public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {

@Autowired
UserRepository userRepository;

private static final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);

public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
    Optional<User> oUser = userRepository.findByUserName(userName);

    if(!oUser.isPresent()){
        throw new UsernameNotFoundException(userName);
    } else {
        logger.info("user found");
    }

    User user = oUser.get();
    return this.buildUserDetails(user);
}

但是在类 DaoAuthenticationProvider 的这个方法中调用了 loadUserByUsername。我怎样才能覆盖这种行为?

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    UserDetails loadedUser;
    try {
        loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    } catch (UsernameNotFoundException var6) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
        }

        throw var6;
    } catch (Exception var7) {
        throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
    }

    if (loadedUser == null) {
        throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
    } else {
        return loadedUser;
    }
}

解决方案

我的 WebSecurityConfig 使用 customDaoAuthenticationProvider

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    UserDetailsServiceExtended userDetailsServiceExtended;
    @Autowired
    PasswordEncoder passwordEncoder;
    @Autowired
    private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    protected TokenAuthenticationService tokenAuthenticationService;

    @Value("${web.security.debug}")
    private boolean debug;

    public WebSecurityConfig() { super(false);}

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors()
            .and()
            .csrf()
            .disable()
            .authorizeRequests()
            .antMatchers("/api/**").authenticated();
        http
            .exceptionHandling()
            .authenticationEntryPoint(customAuthenticationEntryPoint);

        http
            .addFilterBefore(customEmailPasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), CustomEmailPasswordAuthenticationFilter.class)
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    public CustomEmailPasswordAuthenticationFilter customEmailPasswordAuthenticationFilter() throws Exception {
        CustomEmailPasswordAuthenticationFilter filter = new CustomEmailPasswordAuthenticationFilter();
        filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(authenticationFailureHandler);
        filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/users/authenticate", "POST"));
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Bean
    public CustomDaoAuthenticationProvider daoAuthenticationProvider() {
        CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
        authenticationProvider.setPasswordEncoder(this.passwordEncoder);
        authenticationProvider.setUserDetailsService(userDetailsServiceExtended);
        return authenticationProvider;
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
        auth.userDetailsService(this.userDetailsServiceExtended).passwordEncoder(this.passwordEncoder);
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

标签: javaspringsecurity

解决方案


要回答您的直接问题,retrieveUser不能被覆盖。不仅是它final,表示它不能被覆盖,它是受保护的,这意味着你不能从org.springframework.security.authentication.dao包外部访问它。

显然,如果没有解决方案,我不会回答。

Spring 最大的优势在于它的抽象层。很多人对它的使用有误解,但是简单来说,只要一个类扩展了与默认类相同的抽象类,就可以通过使用@Bean注解来代替。

所以在你的情况下,DaoAuthenticationProviderextends AbstractUserDetailsAuthenticationProvider。按照这个逻辑,只要我们创建一个扩展AbstractUserDetailsAuthenticationProvider并相应配置它的类,我们应该能够替换DaoAuthenticationProvider.

让我们称之为那个类CustomDaoAuthenticationProvider

public class CustomDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

}

此处复制 DaoAuthenticationProvider 的所有内容。

唯一的区别是构造函数和类名应该重命名为DaoAuthenticationProviderto CustomDaoAuthenticationProvider

如果您使用的是不同版本的 Spring,您应该能够DaoAuthenticationProvider从 IDE 导航到源代码。

现在您需要创建一个配置类,我们称之为SecurityConfiguration

@Configuration
@WebSecurity // optional?
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired 
    private UserDetailsService userService; // can be replaced by whatever service implements UserDetailsService

    @Bean
    public CustomDaoAuthenticationProvider daoAuthenticationProvider() {
        CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        authenticationProvider.setUserDetailsService(userService);
        System.out.println("Using my custom DaoAuthenticationProvider");
        return authenticationProvider;
    }

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


    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    // ...

}

以下配置应该告诉 Spring 使用CustomDaoAuthenticationProvider而不是DaoAuthenticationProvider.

我对它进行了简短的测试,它应该可以工作。从那里,您可以根据需要retrieveUser直接修改CustomDaoAuthenticationProvider

祝你好运!


推荐阅读