You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security报错:A UserDetailsService must be set 求助

Spring Security Bean创建异常排查:UserDetailsService must be set

问题背景

尝试运行他人的Maven项目,升级至2.5.0版本后因WebSecurityConfigurerAdapter相关问题降级,当前登录功能失效,抛出Bean创建异常:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authenticationProvider' defined in class path resource com/shop/WebSecurityConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: A UserDetailsService must be set

Caused by: java.lang.IllegalArgumentException: A UserDetailsService must be set
        at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.7.jar!/:5.3.7]
        at org.springframework.security.authentication.dao.DaoAuthenticationProvider.doAfterPropertiesSet(DaoAuthenticationProvider.java:85) ~[spring-security-core-5.5.0.jar!/:5.5.0]

相关代码

WebSecurityConfig.java

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private MyUserDetailsService myUserDetailsService;

@Autowired
public void setMyUserDetailsService(MyUserDetailsService myUserDetailsService) {
    this.myUserDetailsService = myUserDetailsService;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .antMatchers("/product/**","/","/home**","/user/**","/registration**","/products**","/default**").permitAll()
            .antMatchers("/resources/**", "/static/**","/bower_components/**","/js/**","/css/**","/images/**").permitAll()
            .antMatchers("/cabinet**","/principle/**" ).authenticated()
            .antMatchers("/**").hasAuthority(String.valueOf(Role.ADMIN))
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/default")
            .successForwardUrl("/default")
            .permitAll()
            .and()
        .logout()
            .permitAll().deleteCookies("JSESSIONID").and()
        .exceptionHandling().accessDeniedPage("/accessDenied");
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProvider())
            .inMemoryAuthentication()
            .withUser("user").password("pass").roles(String.valueOf(Role.USER)).and()
            .withUser("admin").password("pass").roles(String.valueOf(Role.ADMIN));
}

@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authProvider
            = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(myUserDetailsService);
    authProvider.setPasswordEncoder(passwordEncoder());
    return authProvider;
}

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

MyUserDetailsService.java

@Service
public class MyUserDetailsService implements UserDetailsService {
private static Logger LOG = LoggerFactory.getLogger(MyUserDetailsService.class);

private UserRepository userRepository;

@Autowired
public void setUserRepository(UserRepository userRepository) {
    this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = userRepository.findByEmail(username);
    if (user == null) {
        throw new UsernameNotFoundException(username);
    }
    LOG.info("Finded user : [" + username + "]");
    return new MyUserPrincipal(user);
}


public class MyUserPrincipal extends User implements UserDetails {
    private User user;

    private MyUserPrincipal(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList(user.getRole().toString());
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return user.getIsAccountNonLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

排查与解决思路

1. 依赖注入时机问题(核心原因)

authenticationProvider Bean创建时,类中的myUserDetailsService还未通过setter完成注入,导致为null。Spring中@Bean方法的执行可能早于@Autowired setter的触发。

解决方式二选一:

  • 改用构造方法注入:

    private final MyUserDetailsService myUserDetailsService;
    
    // Spring 4.3+无需@Autowired,构造方法自动注入
    public WebSecurityConfig(MyUserDetailsService myUserDetailsService) {
        this.myUserDetailsService = myUserDetailsService;
    }
    

    删除原有的@Autowired setter方法。

  • 直接在@Bean方法中通过参数注入:

    @Bean
    public DaoAuthenticationProvider authenticationProvider(MyUserDetailsService myUserDetailsService) {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(myUserDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }
    

    同时删除类中定义的myUserDetailsService字段和对应的setter。

2. 认证逻辑冲突问题

configureGlobal中同时配置了authenticationProvider(数据库认证)和inMemoryAuthentication(内存用户),两者会共存但可能导致认证优先级混乱。如果需要使用数据库用户认证,建议移除内存用户配置;若要保留内存用户,需确保密码编码一致。

3. 密码编码一致性问题

内存用户的密码是明文"pass",但DaoAuthenticationProvider使用BCryptPasswordEncoder,内存用户登录会因密码不匹配失败。若保留内存用户,需修改为编码后的密码:

.withUser("user").password(passwordEncoder().encode("pass")).roles(String.valueOf(Role.USER))

4. MyUserPrincipal类设计优化

MyUserPrincipal继承了Spring Security的User类,同时持有自定义User对象,容易导致字段/方法混淆。建议改为仅实现UserDetails接口:

public class MyUserPrincipal implements UserDetails {
    private final User user;

    public MyUserPrincipal(User user) {
        this.user = user;
    }

    // 实现UserDetails所有方法,基于user对象返回对应属性
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList(user.getRole().toString());
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return user.getIsAccountNonLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

内容的提问来源于stack exchange,提问作者Knut

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 15:33:34