如何在Spring Boot服务生成的JWT令牌中添加额外自定义字段?
问题1解答
教程中基于UserDetails生成JWT是Spring Security体系下的常规实现逻辑,核心原因有两个:
UserDetails是Spring Security内置的标准用户信息接口,整个身份校验、权限拦截的默认流程都基于这个接口实现,直接使用它不需要额外改造Spring Security的原生验证链路,能减少冗余代码,降低适配成本。- 实现业务层和安全层的解耦,业务
UserDTO可能会随着需求迭代频繁修改字段,而安全层依赖的UserDetails核心方法稳定,不会因为业务字段调整影响安全链路的逻辑。
当然你完全可以直接用UserDTO生成令牌,只是需要自己补充适配Spring Security验证流程的相关代码,教程的写法是兼顾通用性和可维护性的最优选择。
问题2解答
有两种成熟的实现方案可以把额外信息写入JWT:
方案1:自定义UserDetails实现类
不用Spring Security自带的org.springframework.security.core.userdetails.User实现,自己编写类实现UserDetails接口,在类中新增你需要的额外字段即可,示例代码如下:
public class CustomUserDetails implements UserDetails { // Spring Security要求的标准字段 private String username; private String password; private Collection<? extends GrantedAuthority> authorities; // 自定义额外字段 private Long userId; private String userNickname; // 构造方法,传入UserDTO填充所有字段 public CustomUserDetails(User user) { this.username = user.getEMail(); this.password = user.getPswd(); // 填充权限的逻辑和原有代码一致 this.authorities = user.getUserTypes().stream() .map(UserType::getOperations) .flatMap(Set::stream) .map(op -> new SimpleGrantedAuthority(op.getName())) .distinct() .collect(Collectors.toList()); // 填充自定义字段 this.userId = user.getId(); this.userNickname = user.getNickname(); } // 实现UserDetails接口的所有抽象方法,不需要校验的字段(如账号过期、锁定等)直接返回true即可 @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; } // 额外字段的getter方法 public Long getUserId() { return userId; } public String getUserNickname() { return userNickname; } }
之后修改loadUserByUsername方法,直接返回你自定义的CustomUserDetails实例:
@Override public UserDetails loadUserByUsername(String UserId) throws UsernameNotFoundException { // 原有参数校验、查询User的逻辑不变 User user = this.GetHttpValue(UserId); // 直接返回自定义的UserDetails实例 return new CustomUserDetails(user); }
最后修改doGenerateToken方法,把自定义字段写入JWT:
private String doGenerateToken(Map<String, Object> claims, UserDetails userDetails) { final Date createdDate = clock.now(); final Date expirationDate = calculateExpirationDate(createdDate); // 强转为自定义类型获取额外字段 CustomUserDetails customUser = (CustomUserDetails) userDetails; return Jwts.builder() .setClaims(claims) .setSubject(customUser.getUsername()) .claim("authorities", customUser.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())) // 写入自定义字段 .claim("userId", customUser.getUserId()) .claim("userNickname", customUser.getUserNickname()) .setIssuedAt(createdDate) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret().getBytes()) .compact(); }
方案2:直接使用claims参数传值
如果你只需要临时加少量字段,不需要改造UserDetails,可以直接把额外字段塞入doGenerateToken的第一个入参claims中,setClaims方法会自动把所有Map中的键值对写入JWT payload:
// 调用doGenerateToken之前填充自定义字段 Map<String, Object> extraClaims = new HashMap<>(); extraClaims.put("userId", user.getId()); extraClaims.put("userNickname", user.getNickname()); // 调用生成方法 String token = doGenerateToken(extraClaims, userDetails);
注意:JWT的payload是Base64编码的明文,不要写入密码、身份证号等敏感信息。
内容的提问来源于stack exchange,提问作者AndreaNobili
相关产品推荐
相关产品推荐

