Spring Security如何拿到登录用户的信息

书接上文,我们刚实现了一个简单的登录功能,那么我们要如何才能拿到登录用户的具体信息呢?我进行了一下整理

"登录用户的信息" 是保存在 SecurityContext 里的,可以通过多种方式获取这个用户的信息,下面是最常用的方式:

在 Controller 里用 @AuthenticationPrincipal

java 复制代码
@GetMapping("/me")
public String getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
    return "当前登录用户:" + userDetails.getUsername();
}

这个方式适用于:你使用了 Spring Security 默认的 UserDetailsService 或者自定义了用户实体实现了 UserDetails 接口。

SecurityContextHolder.getContext().getAuthentication()

java 复制代码
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();

if (principal instanceof UserDetails userDetails) {
    System.out.println("用户名:" + userDetails.getUsername());
}

这个是 最底层、最万能的方式,可以在任何地方(包括 Service 层)使用。

用 Spring 注入 Principal 对象(Controller 中)

java 复制代码
@GetMapping("/me")
public String getCurrentUser(Principal principal) {
    return "当前登录用户:" + principal.getName();
}

同样的,这个方式也适用于:你使用了 Spring Security 默认的 UserDetailsService 或者自定义了用户实体实现了 UserDetails 接口。

用 HttpServletRequest.getUserPrincipal()

java 复制代码
@GetMapping("/me")
public String getUser(HttpServletRequest request) {
    Principal principal = request.getUserPrincipal();
    return principal.getName();
}

这个也常见于过滤器或 Servlet 场景。

自定义实现UserDetails接口

假设我们已经有了一个实体类TUser,如下:

java 复制代码
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推荐的是后者,原因如下:

  1. 职责分离(Separation of Concerns)
    • TUser 是数据库实体类,只关注"数据结构"和"持久化";
    • LoginUser 是安全上下文中的用户视图,关注"登录逻辑"和"权限校验"。

👉 如果你让 TUser 实现 UserDetails,就把认证逻辑和数据库模型耦合在了一起,违背了单一职责原则(SRP)。

  1. 避免污染实体类

UserDetails 接口有七八个方法(用户名、密码、权限、状态等),可能不是你 TUser 表实际需要的字段;

将这些方法塞进实体类里,会让 TUser 看起来非常臃肿、难维护。

  1. 增强灵活性,方便扩展

LoginUser 可以自由扩展,比如你想额外在 LoginUser 里放 JWT Token、IP 地址、登录时间等字段;

而 TUser 是实体类,不能随便乱加跟表无关的字段,否则容易导致 MyBatis 查询异常或字段映射错误。

  1. 便于做权限模型嵌套

假设以后你设计 RBAC 权限系统,LoginUser 可以包装更多内容:

java 复制代码
public class LoginUser implements UserDetails {
    private TUser user;
    private List<String> roles;
    private List<String> permissions;
}

如果用 TUser 本身,结构就固定死了,扩展权限字段很不优雅。

  1. 跟框架使用方式一致
    Spring Security + MyBatis Plus 实战项目里,绝大多数工程师都选择使用封装类(比如 LoginUser),而不是直接污染实体类。你在企业代码里也会常见:
java 复制代码
public class JwtUser implements UserDetails {
    private final TUser user;
}

ok最终来实现一下LoginUser:

java 复制代码
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;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 你可以从 user 表中读取权限字段并转换为 Authority,这里先返回空集合
        return Collections.emptyList();
    }

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

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

    @Override
    public boolean isAccountNonExpired() {
        return user.getAccountNoExpired() == 1;
    }

    @Override
    public boolean isAccountNonLocked() {
        return user.getAccountNoLocked() == 1;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return user.getCredentialsNoExpired() == 1;
    }

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

然后就可以通过以下方式,使得返回给security框架的UserDetails是我们自己定义的,也就是包含登录用户信息的对象。现在你在用前面提到的四种方式去处理获取数据就行。

java 复制代码
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 {

    @Autowired
    private TUserService tUserService;

    @Override
    public 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层的方式:

java 复制代码
@GetMapping("/me")
public TUser getLoginUser(@AuthenticationPrincipal LoginUser loginUser) {
    return loginUser.getUser();
}

在其他层的方式

java 复制代码
@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的方式也可以

java 复制代码
@GetMapping("/me")
public String getCurrentUser(Principal principal) {
    if (principal instanceof LoginUser loginUser) {
        return "当前登录用户:" + loginUser.getTUser().getName();
    }
    return "未知用户";
}
相关推荐
谢尔登3 小时前
【React】常用的状态管理库比对
前端·spring·react.js
编程乐学(Arfan开发工程师)3 小时前
56、原生组件注入-原生注解与Spring方式注入
java·前端·后端·spring·tensorflow·bug·lua
周某某~4 小时前
七.适配器模式
java·设计模式·适配器模式
奔跑的小十一5 小时前
JDBC接口开发指南
java·数据库
刘大猫.5 小时前
业务:资产管理功能
java·资产管理·资产·资产统计·fau·bpb·mcb
YuTaoShao6 小时前
Java八股文——JVM「内存模型篇」
java·开发语言·jvm
开开心心就好6 小时前
电脑扩展屏幕工具
java·开发语言·前端·电脑·php·excel·batch
零叹7 小时前
篇章十 数据结构——排序
java·数据结构·算法·排序算法
一个有女朋友的程序员7 小时前
Spring Boot 整合 Smart-Doc:零注解生成 API 文档,告别 Swagger
java·spring boot·smart-doc