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; } }
现咨询:
- 当前获取登录用户名的实现是否正确?
- 如何获取完整的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
相关产品推荐
相关产品推荐

