Spring Security:如何在安全上下文存储额外登录用户信息
如何在Spring Security Context中存储自定义用户信息
其实Spring Security在设计时就考虑到了自定义用户信息的需求,最标准的做法就是扩展它的UserDetails接口——这个接口是Spring Security用来承载用户核心身份信息的"容器",我们可以在里面添加任何业务需要的字段(比如用户ID、邮箱、分支ID)。下面一步步拆解实现过程:
1. 创建自定义UserDetails实现类
我们需要定义一个类实现UserDetails接口,同时添加我们需要的额外字段,还要实现序列化(因为Security Context可能会被存入Session,需要支持序列化)。
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.io.Serializable; public class CustomUserDetails implements UserDetails, Serializable { // 自定义额外字段 private Long userId; private String email; private Long branchId; // UserDetails要求的核心字段 private String username; private String password; private Collection<? extends GrantedAuthority> authorities; private boolean accountNonExpired; private boolean accountNonLocked; private boolean credentialsNonExpired; private boolean enabled; // 构造方法:用来封装从数据源获取的完整用户信息 public CustomUserDetails(Long userId, String email, Long branchId, String username, String password, Collection<? extends GrantedAuthority> authorities, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled) { this.userId = userId; this.email = email; this.branchId = branchId; this.username = username; this.password = password; this.authorities = authorities; this.accountNonExpired = accountNonExpired; this.accountNonLocked = accountNonLocked; this.credentialsNonExpired = credentialsNonExpired; this.enabled = enabled; } // 自定义字段的getter方法(供业务代码调用) public Long getUserId() { return userId; } public String getEmail() { return email; } public Long getBranchId() { return branchId; } // 重写UserDetails的所有默认方法 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return accountNonExpired; } @Override public boolean isAccountNonLocked() { return accountNonLocked; } @Override public boolean isCredentialsNonExpired() { return credentialsNonExpired; } @Override public boolean isEnabled() { return enabled; } }
2. 实现自定义UserDetailsService
这个类负责从你的数据源(比如数据库)加载用户的完整信息,并封装成上面的CustomUserDetails对象返回给Spring Security。
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.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Service public class CustomUserDetailsService implements UserDetailsService { // 注入你的用户Repository(假设你用JPA) private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库查询用户完整信息 UserEntity user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用户不存在:" + username)); // 将用户角色转换为Spring Security需要的GrantedAuthority集合 List<SimpleGrantedAuthority> authorities = user.getRoles().stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())) .collect(Collectors.toList()); // 封装成CustomUserDetails返回 return new CustomUserDetails( user.getId(), user.getEmail(), user.getBranchId(), user.getUsername(), user.getPassword(), // 注意:密码必须是加密后的,Spring Security会自动验证 authorities, user.isAccountNonExpired(), user.isAccountNonLocked(), user.isCredentialsNonExpired(), user.isEnabled() ); } }
3. 配置Spring Security使用自定义服务
在你的Security配置类中,指定使用我们的CustomUserDetailsService,并配置密码编码器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { private final CustomUserDetailsService customUserDetailsService; public SecurityConfig(CustomUserDetailsService customUserDetailsService) { this.customUserDetailsService = customUserDetailsService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .formLogin(form -> form.permitAll()) // 这里用表单登录,你也可以换成JWT/OAuth2等 .logout(logout -> logout.permitAll()); return http.build(); } @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder auth = http.getSharedObject(AuthenticationManagerBuilder.class); auth.userDetailsService(customUserDetailsService) .passwordEncoder(passwordEncoder()); // 指定密码加密器 return auth.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
4. 在业务代码中获取自定义用户信息
现在用户登录后,Security Context中的Principal就是我们的CustomUserDetails对象,直接强转后就能拿到额外字段:
// 获取Authentication对象 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 安全地强转为自定义用户类 if (authentication.getPrincipal() instanceof CustomUserDetails customUser) { Long userId = customUser.getUserId(); String email = customUser.getEmail(); Long branchId = customUser.getBranchId(); // 在这里使用这些信息做业务处理,比如关联分支数据、发送邮件等 }
内容的提问来源于stack exchange,提问作者Vivek
相关产品推荐
相关产品推荐

