基于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




