You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于JWT认证,如何在@RestController中获取当前登录用户ID?

嘿,这个问题我熟!在JWT认证模式下,咱们不需要再依赖Session来拿用户ID了——因为JWT本身就携带了用户的身份信息,只要正确解析令牌就能拿到咱们需要的userId。我给你分两种方式来实现,一种是手动解析,另一种是结合Spring Security的优雅方案,你可以根据自己的项目情况选:

解决方案

方式一:手动解析JWT令牌

这种方式适合没有集成Spring Security的场景,核心思路是从请求头里提取JWT令牌,然后解析出存储在Claims里的用户ID。

1. 先写个JWT解析工具类

假设你用的是JJWT库(Java里常用的JWT处理库),先实现一个工具类来解析令牌:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public class JwtUtils {
    // 注意:这个密钥要和你生成JWT时用的密钥完全一致,并且不要硬编码,建议从配置文件读取
    private static final String SECRET_KEY = "your-secret-key-here";

    // 解析JWT获取Claims(里面包含你存入的用户信息)
    public static Claims extractClaims(String authHeader) {
        // 移除Authorization头里的"Bearer "前缀
        String token = authHeader.replace("Bearer ", "");
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }

    // 从Claims里提取用户ID(前提是你生成JWT时把userId存入了Claims)
    public static Long getUserIdFromToken(String authHeader) {
        Claims claims = extractClaims(authHeader);
        // 这里的"userId"要和你生成JWT时存入的key一致
        return claims.get("userId", Long.class);
    }
}

2. 修改你的接口代码

在接口里通过@RequestHeader获取Authorization头,然后调用工具类拿到用户ID:

@PostMapping("/user/profile")
public ResponseEntity<?> saveProfile(@Valid @RequestBody UserProfileDTO userProfile,
                                     @RequestHeader("Authorization") String authHeader) {
    // 提取当前登录用户的ID
    Long userId = JwtUtils.getUserIdFromToken(authHeader);

    // 接下来就可以用userId去查询数据库里的用户,更新信息了
    // 示例:
    // User currentUser = userRepository.findById(userId)
    //         .orElseThrow(() -> new RuntimeException("用户不存在"));
    // currentUser.setNickname(userProfile.getNickname());
    // currentUser.setEmail(userProfile.getEmail());
    // userRepository.save(currentUser);

    return ResponseEntity.ok("个人信息更新成功");
}

方式二:结合Spring Security使用@AuthenticationPrincipal

如果你的项目已经集成了Spring Security,这种方式更优雅,Spring会帮你处理JWT的验证和用户信息的上下文存储。

1. 自定义UserDetails类

让它包含用户ID,这样Spring Security的认证上下文里就能拿到用户ID:

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;

public class CustomUserDetails implements UserDetails {
    private Long userId;
    private String username;
    private String password;
    private Collection<? extends GrantedAuthority> authorities;

    // 构造方法,从数据库用户对象初始化
    public CustomUserDetails(User user) {
        this.userId = user.getId();
        this.username = user.getUsername();
        this.password = user.getPassword();
        // 这里可以设置用户权限,比如从数据库获取角色
        // this.authorities = user.getRoles().stream()
        //         .map(role -> new SimpleGrantedAuthority(role.getName()))
        //         .collect(Collectors.toList());
    }

    // 提供获取用户ID的方法
    public Long getUserId() {
        return userId;
    }

    // 实现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 true;
    }

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

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

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

2. 配置JWT认证过滤器

让Spring Security在请求到达接口前,自动解析JWT并把用户信息存入上下文:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtUtils jwtUtils;
    private final UserDetailsService userDetailsService;

    // 构造方法注入依赖
    public JwtAuthenticationFilter(JwtUtils jwtUtils, UserDetailsService userDetailsService) {
        this.jwtUtils = jwtUtils;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");

        // 检查Authorization头是否存在且以Bearer开头
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.replace("Bearer ", "");
            // 从JWT里提取用户名
            String username = jwtUtils.extractUsername(token);

            // 如果用户名存在且当前上下文没有认证信息
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                // 从数据库加载用户详情
                CustomUserDetails userDetails = (CustomUserDetails) userDetailsService.loadUserByUsername(username);

                // 验证JWT的有效性
                if (jwtUtils.validateToken(token, userDetails)) {
                    // 创建认证对象并存入上下文
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities()
                    );
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
        }

        // 继续执行后续过滤器
        filterChain.doFilter(request, response);
    }
}

3. 简化接口代码

现在可以直接用@AuthenticationPrincipal注解获取当前用户的详情,拿到用户ID:

@PostMapping("/user/profile")
public ResponseEntity<?> saveProfile(@Valid @RequestBody UserProfileDTO userProfile,
                                     @AuthenticationPrincipal CustomUserDetails currentUser) {
    // 直接从currentUser获取用户ID
    Long userId = currentUser.getUserId();

    // 后续的更新逻辑和之前一样
    // ...

    return ResponseEntity.ok("个人信息更新成功");
}
关键注意事项
  • 生成JWT时要存入用户ID:不管用哪种方式,你在生成登录/注册返回的JWT时,必须把用户的唯一标识(比如userId)存入JWT的Claims中,否则解析时拿不到。
  • 密钥要保密:绝对不能把JWT的签名密钥硬编码在代码里,建议放在配置文件、环境变量或者密钥管理服务中。
  • 验证JWT有效性:一定要验证JWT的签名是否正确、是否过期,避免伪造的令牌被解析使用。

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

火山引擎 最新活动