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

Spring Boot中loadUserByUsername方法冲突问题求助

问题分析与解决方案

你的核心问题有三点:一是强制类型转换错误(把Spring Security的UserDetails转换成自定义Admin/User实体);二是手动认证逻辑不规范,未利用Spring Security的安全上下文机制;三是多个UserDetailsService实现导致的容器冲突,需要明确为不同登录路径绑定对应的服务。

以下是分步解决方法:


1. 修复基础代码错误

补全Service的依赖注入

CustomUserDetailsService中缺少AdminRepository的注入,补充后才能正常查询数据:

@Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private AdminRepository adminRepository; // 新增注入

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Admin admin = adminRepository.findByUsername(username);

        if (admin == null) {
            throw new UsernameNotFoundException(username);
        }

        return org.springframework.security.core.userdetails.User.builder()
                .username(admin.getUsername())
                .password(admin.getPassword())
                .roles(admin.getRole())
                .build();
    }
}

移除错误的强制类型转换

你的控制器中(Admin) adminService.loadUserByUsername(username)是错误的——loadUserByUsername返回的是Spring Security封装的UserDetails对象,不是自定义的Admin实体,直接转换会抛出类型转换异常。


2. 推荐方案:用Spring Security多认证配置规范实现

手动写认证逻辑容易遗漏安全细节(比如用户状态校验、安全上下文存储),建议通过Spring Security的多SecurityFilterChain配置,为两个登录端点绑定各自的UserDetailsService:

编写Security配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    @Qualifier("customUserDetailsService")
    private UserDetailsService adminUserDetailsService;

    @Autowired
    @Qualifier("userService")
    private UserDetailsService userUserDetailsService;

    // 配置密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 管理员认证提供者
    @Bean
    public AuthenticationProvider adminAuthProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(adminUserDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    // 用户认证提供者
    @Bean
    public AuthenticationProvider userAuthProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userUserDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    // 管理员专属安全过滤器链
    @Bean
    public SecurityFilterChain adminSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher("/login-admin", "/admin-dashboard/**") // 匹配管理员相关路径
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .authenticationProvider(adminAuthProvider())
                .formLogin(form -> form
                        .loginProcessingUrl("/login-admin") // 对应登录接口
                        .usernameParameter("username") // 表单用户名参数名
                        .passwordParameter("password") // 表单密码参数名
                        .defaultSuccessUrl("/admin-dashboard") // 登录成功跳转
                        .failureUrl("/login-admin?error=true") // 登录失败跳转
                        .permitAll()
                )
                .logout(logout -> logout.permitAll());

        return http.build();
    }

    // 用户专属安全过滤器链
    @Bean
    public SecurityFilterChain userSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher("/login-user", "/user-dashboard/**") // 匹配用户相关路径
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .authenticationProvider(userAuthProvider())
                .formLogin(form -> form
                        .loginProcessingUrl("/login-user") // 对应登录接口
                        .usernameParameter("email") // 用email作为用户名参数
                        .passwordParameter("password")
                        .defaultSuccessUrl("/user-dashboard")
                        .failureUrl("/login-user?error=true")
                        .permitAll()
                )
                .logout(logout -> logout.permitAll());

        return http.build();
    }
}

简化控制器代码

此时控制器只需负责返回登录页面和仪表盘页面,认证逻辑由Spring Security自动处理:

@Controller
public class MainController {

    // 显示管理员登录页
    @GetMapping("/login-admin")
    public String showAdminLogin() {
        return "login-admin";
    }

    // 显示用户登录页
    @GetMapping("/login-user")
    public String showUserLogin() {
        return "login-user";
    }

    // 管理员仪表盘
    @GetMapping("/admin-dashboard")
    public String adminDashboard() {
        return "admin-dashboard";
    }

    // 用户仪表盘
    @GetMapping("/user-dashboard")
    public String userDashboard() {
        return "user-dashboard";
    }
}

3. 备选方案:保留手动认证逻辑(不推荐)

如果一定要在控制器中手动处理认证,需修正类型转换问题并正确维护安全上下文:

@Controller
public class MainController {
    @Autowired
    @Qualifier("customUserDetailsService")
    private CustomUserDetailsService adminService;

    @Autowired
    @Qualifier("userService")
    private UserService userService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostMapping("/login-admin")
    public String loginAdmin(@RequestParam("username") String username,
                             @RequestParam("password") String password,
                             Model model) {
        try {
            UserDetails adminDetails = adminService.loadUserByUsername(username);
            if (passwordEncoder.matches(password, adminDetails.getPassword())) {
                // 将认证信息存入安全上下文
                Authentication auth = new UsernamePasswordAuthenticationToken(
                        adminDetails, null, adminDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(auth);
                return "redirect:/admin-dashboard";
            }
        } catch (UsernameNotFoundException e) {
            // 捕获用户不存在异常
        }
        model.addAttribute("error", "username or password is wrong.");
        return "login-admin";
    }

    @PostMapping("/login-user")
    public String loginUser(@RequestParam("email") String email,
                            @RequestParam("password") String password,
                            Model model) {
        try {
            UserDetails userDetails = userService.loadUserByUsername(email);
            if (passwordEncoder.matches(password, userDetails.getPassword())) {
                Authentication auth = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(auth);
                return "redirect:/user-dashboard";
            }
        } catch (UsernameNotFoundException e) {
            // 捕获用户不存在异常
        }
        model.addAttribute("error", "email or password is wrong.");
        return "login-user";
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:47:34