Spring Security如何拿到登录用户的信息
书接上文,我们刚实现了一个简单的登录功能,那么我们要如何才能拿到登录用户的具体信息呢?我进行了一下整理
“登录用户的信息” 是保存在 SecurityContext 里的,可以通过多种方式获取这个用户的信息,下面是最常用的方式:
在 Controller 里用 @AuthenticationPrincipal
@GetMapping("/me")
public String getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {return "当前登录用户:" + userDetails.getUsername();
}
这个方式适用于:你使用了 Spring Security 默认的 UserDetailsService 或者自定义了用户实体实现了 UserDetails 接口。
SecurityContextHolder.getContext().getAuthentication()
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();if (principal instanceof UserDetails userDetails) {System.out.println("用户名:" + userDetails.getUsername());
}
这个是 最底层、最万能的方式,可以在任何地方(包括 Service 层)使用。
用 Spring 注入 Principal 对象(Controller 中)
@GetMapping("/me")
public String getCurrentUser(Principal principal) {return "当前登录用户:" + principal.getName();
}
同样的,这个方式也适用于:你使用了 Spring Security 默认的 UserDetailsService 或者自定义了用户实体实现了 UserDetails 接口。
用 HttpServletRequest.getUserPrincipal()
@GetMapping("/me")
public String getUser(HttpServletRequest request) {Principal principal = request.getUserPrincipal();return principal.getName();
}
这个也常见于过滤器或 Servlet 场景。
自定义实现UserDetails接口
假设我们已经有了一个实体类TUser,如下:
package org.pp.springsecurity.entity;import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;@Data
@TableName("t_user")
public class TUser implements Serializable {private Integer id;private String loginAct;private String loginPwd;private String name;private String phone;private String email;private LocalDateTime createTime;private Integer createBy;private LocalDateTime editTime;private Integer editBy;private LocalDateTime lastLoginTime;}
此时我们有两个选择:
- 直接让TUser实现UserDetails接口
- 额外写一个LoginUser实现UserDetails,并包装TUser
GPT推荐的是后者,原因如下:
- 职责分离(Separation of Concerns)
- TUser 是数据库实体类,只关注“数据结构”和“持久化”;
- LoginUser 是安全上下文中的用户视图,关注“登录逻辑”和“权限校验”。
👉 如果你让 TUser 实现 UserDetails,就把认证逻辑和数据库模型耦合在了一起,违背了单一职责原则(SRP)。
- 避免污染实体类
UserDetails 接口有七八个方法(用户名、密码、权限、状态等),可能不是你 TUser 表实际需要的字段;
将这些方法塞进实体类里,会让 TUser 看起来非常臃肿、难维护。
- 增强灵活性,方便扩展
LoginUser 可以自由扩展,比如你想额外在 LoginUser 里放 JWT Token、IP 地址、登录时间等字段;
而 TUser 是实体类,不能随便乱加跟表无关的字段,否则容易导致 MyBatis 查询异常或字段映射错误。
- 便于做权限模型嵌套
假设以后你设计 RBAC 权限系统,LoginUser 可以包装更多内容:
public class LoginUser implements UserDetails {private TUser user;private List<String> roles;private List<String> permissions;
}
如果用 TUser 本身,结构就固定死了,扩展权限字段很不优雅。
- 跟框架使用方式一致
Spring Security + MyBatis Plus 实战项目里,绝大多数工程师都选择使用封装类(比如 LoginUser),而不是直接污染实体类。你在企业代码里也会常见:
public class JwtUser implements UserDetails {private final TUser user;
}
ok最终来实现一下LoginUser:
package org.pp.springsecurity.entity;import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.Collections;@Getter
@AllArgsConstructor
public class LoginUser implements UserDetails {private final TUser user;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {// 你可以从 user 表中读取权限字段并转换为 Authority,这里先返回空集合return Collections.emptyList();}@Overridepublic String getPassword() {return user.getLoginPwd();}@Overridepublic String getUsername() {return user.getLoginAct();}@Overridepublic boolean isAccountNonExpired() {return user.getAccountNoExpired() == 1;}@Overridepublic boolean isAccountNonLocked() {return user.getAccountNoLocked() == 1;}@Overridepublic boolean isCredentialsNonExpired() {return user.getCredentialsNoExpired() == 1;}@Overridepublic boolean isEnabled() {return user.getAccountEnabled() == 1;}
}
然后就可以通过以下方式,使得返回给security框架的UserDetails是我们自己定义的,也就是包含登录用户信息的对象。现在你在用前面提到的四种方式去处理获取数据就行。
package org.pp.springsecurity.security;import org.pp.springsecurity.entity.TUser;
import org.pp.springsecurity.service.TUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate TUserService tUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {TUser tUser = tUserService.lambdaQuery().eq(TUser::getLoginAct, username).one();if (tUser == null) {throw new UsernameNotFoundException("用户不存在");}return new LoginUser(tUser);}
}
什么????你问我数据在哪???
你都获取到LoginUser了,它调用.getTUser不久获取到了对应数据库实体类的对象!!!
这里我们还是来讲一下吧
在controller层的方式:
@GetMapping("/me")
public TUser getLoginUser(@AuthenticationPrincipal LoginUser loginUser) {return loginUser.getUser();
}
在其他层的方式
@GetMapping("/me")
public TUser getLoginUser() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();Object principal = authentication.getPrincipal();if (principal instanceof LoginUser loginUser) {return loginUser.getUser(); // LoginUser 包装了 TUser,你可以自定义这个字段}throw new RuntimeException("未登录");
}
你用principle的方式也可以
@GetMapping("/me")
public String getCurrentUser(Principal principal) {if (principal instanceof LoginUser loginUser) {return "当前登录用户:" + loginUser.getTUser().getName();}return "未知用户";
}