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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:54:34