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

Spring Boot项目中获取当前登录用户的技术实现咨询

问题:Spring Boot中获取当前登录用户的实现是否正确?如何获取完整User实体?

我正在Spring Boot项目中尝试获取当前登录用户,以下是我的实体类及关联关系:

User.java

@Entity
@Table(name = "user_account")
public class User {
    @Id
    @Column(unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String email;
    private String username;
    private String userType;
    @OneToOne(mappedBy = "user")
    private BankUserDetails bankUserDetails;
    @OneToOne(mappedBy ="user")
    private SctUserDetails sctUserDetails;
    @Column(length = 60)
    private String password;
    private boolean enabled;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private Collection<Role> roles;
    public User() {
        super();
        this.enabled = true;
    }
    // 省略getter/setter(假设已定义)
}

Role.java

@Entity
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @ManyToMany(mappedBy = "roles")
    private Collection<User> users;
    @ManyToMany()
    @JoinTable(name = "roles_privileges", joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "privilege_id", referencedColumnName = "id"))
    private Collection<Privilege> privileges;
    private String name;
    public Role() {
        super();
    }
    public Role(final String name) {
        super();
        this.name = name;
    }
    // 省略getter/setter
}

Privilege.java

@Entity
public class Privilege {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    @ManyToMany(mappedBy = "privileges")
    private Collection<Role> roles;
    public Privilege() {
        super();
    }
    public Privilege(final String name) {
        super();
        this.name = name;
    }
    // 省略getter/setter
}

目前我在控制器中通过以下代码尝试打印当前登录用户:

@RequestMapping("/admin")
public String adminPage(Model model){
    System.out.println("logged user "+UserController.getLoggedInUser());
    return "admin";
}

在UserController类中定义了如下静态方法来获取当前登录用户:

public static String getLoggedInUser(){
    String username = null;
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    if(principal instanceof UserDetails){
        username = ((UserDetails) principal).getUsername();
    }else {
        username = principal.toString();
    }
    return username;
}

我的Spring Security配置类如下:

@Configuration
@ComponentScan(basePackages = { "com.infodev.pcms.security" })
@EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsService userDetailsService;
    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private CustomLogoutSuccessHandler myLogoutSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;
    /*@Autowired
    private CustomWebAuthenticationDetailsSource authenticationDetailsSource;*/
    private BCryptPasswordEncoder passwordEncoder() {
        return SecurityUtils.passwordEncoder();
    }
    @Autowired
    private UserRepository userRepository;
    public SecSecurityConfig() {
        super();
    }
    private static final String[] PUBLIC_MATCHERS = { "/css/**", "/js/**", "/images/**", "**/", "/newUser", "/forgetPassword", "/login", "/uploads/**", "/assets/**", "/api/updateCardStatus" };
    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider());
    }
    @Override
    public void configure(final WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**","/listAllUsers/**");
    }
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeRequests()
            .antMatchers(PUBLIC_MATCHERS). permitAll().anyRequest().authenticated();
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/login*","/login*", "/logout*", "/signin/**", "/signup/**", "/customLogin", "/user/registration*", "/registrationConfirm*", "/expiredAccount*", "/registration*", "/badUser*", "/user/resendRegistrationToken*" , "/forgetPassword*", "/user/resetPassword*", "/user/changePassword*", "/emailError*", "/resources/**", "/old/user/registration*","/successRegister*","/qrcode*").permitAll()
            .antMatchers("/invalidSession*").anonymous()
            .antMatchers("/user/updatePassword*","/user/savePassword*","/updatePassword*")
            .hasAuthority("CHANGE_PASSWORD_PRIVILEGE")
            .anyRequest().hasAuthority("READ_PRIVILEGE")
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/homepage.html")
            .failureUrl("/login?error=true")
            .successHandler(myAuthenticationSuccessHandler)
            .failureHandler(authenticationFailureHandler)
            .permitAll();
        // @formatter:on
    }
    private AuthenticationProvider authProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }
}

现咨询:

  1. 当前获取登录用户名的实现是否正确?
  2. 如何获取完整的User实体对象?

解答

嘿,咱们一步步梳理你的问题:

1. 当前获取登录用户名的实现是否正确?

整体来说这个实现是正确的,但有几个小细节可以优化:

  • SecurityContextHolder.getContext()默认基于ThreadLocal实现,在Web环境下是安全的——Spring Security会在请求开始时把Authentication绑定到当前线程,请求结束时自动清理。
  • 要注意未登录场景:此时getAuthentication()可能返回null,直接调用getPrincipal()会抛NullPointerException,建议加空判断:
public static String getLoggedInUser(){
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    // 处理未登录或匿名用户的情况
    if (authentication == null || !authentication.isAuthenticated() || authentication.getPrincipal() instanceof String) {
        return null; // 也可以返回"未登录"这类标识字符串
    }
    String username = null;
    Object principal = authentication.getPrincipal();
    if(principal instanceof UserDetails){
        username = ((UserDetails) principal).getUsername();
    }else {
        username = principal.toString();
    }
    return username;
}
  • 另外,静态方法虽然能用,但在Spring环境下更推荐用依赖注入或注解方式获取,代码会更解耦,也便于测试。

2. 如何获取完整的User实体对象?

这里有几种常用方案,你可以按需选择:

方案一:通过用户名从UserRepository查询

这是最直接的方式,拿到用户名后注入UserRepository查询完整实体:

// 在控制器中注入UserRepository
@Autowired
private UserRepository userRepository;

@RequestMapping("/admin")
public String adminPage(Model model){
    String username = UserController.getLoggedInUser();
    User currentUser = userRepository.findByUsername(username); // 需要在UserRepository中定义该方法
    System.out.println("完整登录用户:" + currentUser);
    return "admin";
}

对应的UserRepository需要添加查询方法:

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

方案二:让自定义UserDetails返回User实体

你的MyUserDetailsService实现了UserDetailsService接口,可以在loadUserByUsername中返回包含完整User的自定义UserDetails,这样后续能直接从Principal中获取实体:

首先创建自定义UserDetails类:

public class CustomUserDetails implements UserDetails {
    private User user;

    public CustomUserDetails(User user) {
        this.user = user;
    }

    // 实现UserDetails的所有方法,直接复用user的属性
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return user.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return user.isEnabled();
    }

    // 提供获取User实体的方法
    public User getUser() {
        return user;
    }
}

修改MyUserDetailsService的loadUserByUsername方法:

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
        return new CustomUserDetails(user);
    }
}

之后获取用户时直接转换即可:

public static User getLoggedInUserEntity(){
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication == null || !authentication.isAuthenticated()) {
        return null;
    }
    Object principal = authentication.getPrincipal();
    if(principal instanceof CustomUserDetails){
        return ((CustomUserDetails) principal).getUser();
    }
    return null;
}

方案三:使用@AuthenticationPrincipal注解(推荐)

Spring Security提供了@AuthenticationPrincipal注解,可以直接在控制器方法参数中注入当前的UserDetails,非常便捷:

如果用了上面的CustomUserDetails,可以这样写:

@RequestMapping("/admin")
public String adminPage(Model model, @AuthenticationPrincipal CustomUserDetails customUserDetails){
    User currentUser = customUserDetails.getUser();
    System.out.println("完整登录用户:" + currentUser);
    return "admin";
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:02:50