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
相关产品推荐
相关产品推荐

