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

Spring Security自定义ShopmeUserDetails无法认证问题排查

Spring Security自定义UserDetails认证失败排查

当UserDetailsService的loadUserByUsername方法返回自定义ShopmeUserDetails(user)实例时,Spring Security无法完成用户认证;但返回org.springframework.security.core.userdetails.User实例时认证正常。以下为两种场景的代码示例及ShopmeUserDetails类的实现代码:

无法认证的代码(返回ShopmeUserDetails)

package co.ichat.ichatweb.impl;

import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import co.ichat.ichatweb.model.ShopmeUserDetails;
import co.ichat.ichatweb.model.User;
import co.ichat.ichatweb.repository.UserRepository;
import co.ichat.ichatweb.service.IUserService;

@Service
public class UserServiceImpl implements IUserService, UserDetailsService{
    
    @Autowired
    private UserRepository userRepo;
    
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public Integer saveUser(User user) {
        String passwd= user.getPassword();
        String encodedPasswod = passwordEncoder.encode(passwd);
        user.setPassword(encodedPasswod);
        user = userRepo.save(user);
        return user.getId();
    }
    
    @Override
    public UserDetails loadUserByUsername(String email) 
            throws UsernameNotFoundException {
        
        Optional<User> opt = userRepo.findUserByEmail(email);
        
        if(opt.isEmpty())
        {
                
            throw new UsernameNotFoundException("User with email: " +email +" not found !");
                
        }
        else {
            User user = opt.get();
            // return new org.springframework.security.core.userdetails.User(
            //      user.getEmail(),
            //      user.getPassword(),
            //      user.getRoles()
            //      .stream()
            //      .map(role-> new SimpleGrantedAuthority(role))
            //      .collect(Collectors.toSet())
            // );
            return new ShopmeUserDetails(user);
        }
        
    }
}

认证正常的代码(返回默认User)

package co.ichat.ichatweb.impl;

import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import co.ichat.ichatweb.model.ShopmeUserDetails;
import co.ichat.ichatweb.model.User;
import co.ichat.ichatweb.repository.UserRepository;
import co.ichat.ichatweb.service.IUserService;

@Service
public class UserServiceImpl implements IUserService, UserDetailsService{
    
    @Autowired
    private UserRepository userRepo;
    
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public Integer saveUser(User user) {
        String passwd= user.getPassword();
        String encodedPasswod = passwordEncoder.encode(passwd);
        user.setPassword(encodedPasswod);
        user = userRepo.save(user);
        return user.getId();
    }
    
    @Override
    public UserDetails loadUserByUsername(String email) 
            throws UsernameNotFoundException {
        
        Optional<User> opt = userRepo.findUserByEmail(email);
        
        if(opt.isEmpty())
        {
                
            throw new UsernameNotFoundException("User with email: " +email +" not found !");
                
        }
        else {
            User user = opt.get();
            return new org.springframework.security.core.userdetails.User(
                    user.getEmail(),
                    user.getPassword(),
                    user.getRoles()
                    .stream()
                    .map(role-> new SimpleGrantedAuthority(role))
                    .collect(Collectors.toSet())
            );
            //return new ShopmeUserDetails(user);
        }
        
    }
}

ShopmeUserDetails类实现

package co.ichat.ichatweb.model;

import java.util.*;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class ShopmeUserDetails implements UserDetails {
private User user;

    public ShopmeUserDetails(User user) {
        this.user = user;
    }
    
    //Issue with below function
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<String> roles = user.getRoles();
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
    
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
    
    @Override
    public String getPassword() {
        return user.getPassword();
    }
    
    @Override
    public String getUsername() {
        return user.getEmail();
    }
    
    
    public String getEmail() {
        return user.getEmail();
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    
    public int getId() {
        return user.getId();
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    
    @Override
    public boolean isEnabled() {
        return user.isEnabled();
    }

}

问题原因分析

认证失败的核心原因是自定义ShopmeUserDetails与默认User类的用户启用状态判定逻辑不一致:

  • 默认org.springframework.security.core.userdetails.User使用的构造器(User(String username, String password, Collection<? extends GrantedAuthority> authorities))会将enabled、accountNonExpired等状态字段默认设为true,完全忽略数据库中用户的实际enabled值。
  • 而你的ShopmeUserDetails类中,isEnabled()方法直接返回user.isEnabled(),如果数据库中该用户的enabled字段为false(比如未激活、被禁用),Spring Security会判定用户不可用,直接拒绝认证请求。

额外验证了其他逻辑:

  • getAuthorities()方法的List转Set差异不影响认证,Spring Security接受任何类型的Collection<? extends GrantedAuthority>。
  • getPassword()和getUsername()的逻辑在两种场景下完全一致,不存在密码或用户名匹配问题。

修复方案

方案1:和默认User类保持一致(强制启用用户)

修改ShopmeUserDetails的isEnabled()方法:

@Override
public boolean isEnabled() {
    return true; // 替换原来的return user.isEnabled();
}

方案2:按用户实际状态控制(确保数据库中enabled为true)

如果需要保留用户状态的判定逻辑,需确保数据库中目标用户的enabled字段值为true,或者在用户注册/激活流程中正确设置该字段。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 18:26:06