如何在不修改generateToken参数的前提下动态设置JWT的subject
问题
需要根据请求体中的username动态生成JWT的subject,但不想修改generateToken方法的参数(涉及大量代码改动)。当前JwtService类的generateToken方法使用配置文件读取的固定subject生成JWT,想知道如何在创建token前动态修改subject,且不改动generateToken的参数。
现有服务类代码
@Component @RequiredArgsConstructor public class JwtService { @Value("${issuer}") private String issuer; @Value("${kid}") private String keyId; @Value("#{'${audience}'.split(',')}") private List<String> audiences; @Value("#{'${subject}'.split(',')}") private List<String> subject; private final JwtKeyProvider jwtKeyProvider; public String generateToken() throws JoseException { JwtClaims claims = new JwtClaims(); claims.setIssuer(issuer); claims.setAudience(Lists.newArrayList(audiences)); claims.setExpirationTimeMinutesInTheFuture(60); claims.setJwtId(keyId); claims.setIssuedAtToNow(); claims.setNotBeforeMinutesInThePast(0); claims.setSubject(subject); JsonWebSignature jws = new JsonWebSignature(); jws.setPayload(claims.toJson()); jws.setHeader("typ", "JWT"); jws.setKey(jwtKeyProvider.getPrivateKey()); jws.setKeyIdHeaderValue(keyId); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); String jwt = jws.getCompactSerialization(); return jwt; } }
现有控制器代码
@RestController @RequiredArgsConstructor public class JWTController { private final JwtService jwtService; @PostMapping("/getToken") public ResponseEntity getJwt(@RequestBody JwtRequest request) throws JoseException { return ResponseEntity.ok( JwtResponse.builder() .token(jwtService.generateToken()) .build() ); } }
解决方案
方法1:用ThreadLocal传递username
通过ThreadLocal在控制器中设置当前请求的username,JwtService读取该值作为subject,完全不需要修改generateToken的参数。
- 修改JwtService:
@Component @RequiredArgsConstructor public class JwtService { @Value("${issuer}") private String issuer; @Value("${kid}") private String keyId; @Value("#{'${audience}'.split(',')}") private List<String> audiences; @Value("#{'${subject}'.split(',')}") private List<String> defaultSubject; private final JwtKeyProvider jwtKeyProvider; private final ThreadLocal<String> currentUsername = new ThreadLocal<>(); // 添加设置username的方法 public void setCurrentUsername(String username) { currentUsername.set(username); } // 添加清理方法,防止内存泄漏 public void clearCurrentUsername() { currentUsername.remove(); } public String generateToken() throws JoseException { JwtClaims claims = new JwtClaims(); claims.setIssuer(issuer); claims.setAudience(Lists.newArrayList(audiences)); claims.setExpirationTimeMinutesInTheFuture(60); claims.setJwtId(keyId); claims.setIssuedAtToNow(); claims.setNotBeforeMinutesInThePast(0); // 优先用ThreadLocal中的username,无值则用默认配置 String targetSubject = currentUsername.get(); if (targetSubject != null) { claims.setSubject(targetSubject); } else { claims.setSubject(defaultSubject); } JsonWebSignature jws = new JsonWebSignature(); jws.setPayload(claims.toJson()); jws.setHeader("typ", "JWT"); jws.setKey(jwtKeyProvider.getPrivateKey()); jws.setKeyIdHeaderValue(keyId); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); String jwt = jws.getCompactSerialization(); clearCurrentUsername(); // 清理ThreadLocal return jwt; } }
- 修改控制器:
@RestController @RequiredArgsConstructor public class JWTController { private final JwtService jwtService; @PostMapping("/getToken") public ResponseEntity getJwt(@RequestBody JwtRequest request) throws JoseException { jwtService.setCurrentUsername(request.getUsername()); return ResponseEntity.ok( JwtResponse.builder() .token(jwtService.generateToken()) .build() ); } }
方法2:从请求上下文获取username
利用Spring的RequestContextHolder直接获取当前请求,解析请求体得到username。注意请求体只能读取一次,若控制器已读取过请求体,需先通过过滤器包装请求。
- 修改JwtService:
@Component @RequiredArgsConstructor public class JwtService { @Value("${issuer}") private String issuer; @Value("${kid}") private String keyId; @Value("#{'${audience}'.split(',')}") private List<String> audiences; @Value("#{'${subject}'.split(',')}") private List<String> defaultSubject; private final JwtKeyProvider jwtKeyProvider; private final ObjectMapper objectMapper; public String generateToken() throws JoseException, IOException { JwtClaims claims = new JwtClaims(); claims.setIssuer(issuer); claims.setAudience(Lists.newArrayList(audiences)); claims.setExpirationTimeMinutesInTheFuture(60); claims.setJwtId(keyId); claims.setIssuedAtToNow(); claims.setNotBeforeMinutesInThePast(0); // 从当前请求上下文获取请求对象 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 解析请求体得到username JwtRequest jwtRequest = objectMapper.readValue(request.getInputStream(), JwtRequest.class); if (jwtRequest.getUsername() != null) { claims.setSubject(jwtRequest.getUsername()); } else { claims.setSubject(defaultSubject); } JsonWebSignature jws = new JsonWebSignature(); jws.setPayload(claims.toJson()); jws.setHeader("typ", "JWT"); jws.setKey(jwtKeyProvider.getPrivateKey()); jws.setKeyIdHeaderValue(keyId); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); return jws.getCompactSerialization(); } }
- 若控制器已读取请求体,添加过滤器包装请求:
@Component public class ContentCachingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 包装请求,允许重复读取请求体 ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); filterChain.doFilter(wrappedRequest, response); } }
方法3:添加重载方法(最小侵入)
不修改原有generateToken()方法,新增带参数的重载方法,原有方法调用重载方法并传入默认subject。这样仅需修改控制器的调用逻辑,其他调用generateToken()的代码不受影响。
- 修改JwtService:
@Component @RequiredArgsConstructor public class JwtService { @Value("${issuer}") private String issuer; @Value("${kid}") private String keyId; @Value("#{'${audience}'.split(',')}") private List<String> audiences; @Value("#{'${subject}'.split(',')}") private List<String> defaultSubject; private final JwtKeyProvider jwtKeyProvider; // 原有方法保持不变,调用重载方法传递默认subject public String generateToken() throws JoseException { return generateToken(defaultSubject); } // 新增带参数的重载方法,用于动态设置subject public String generateToken(String subject) throws JoseException { JwtClaims claims = new JwtClaims(); claims.setIssuer(issuer); claims.setAudience(Lists.newArrayList(audiences)); claims.setExpirationTimeMinutesInTheFuture(60); claims.setJwtId(keyId); claims.setIssuedAtToNow(); claims.setNotBeforeMinutesInThePast(0); claims.setSubject(subject); JsonWebSignature jws = new JsonWebSignature(); jws.setPayload(claims.toJson()); jws.setHeader("typ", "JWT"); jws.setKey(jwtKeyProvider.getPrivateKey()); jws.setKeyIdHeaderValue(keyId); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); return jws.getCompactSerialization(); } }
- 修改控制器:
@RestController @RequiredArgsConstructor public class JWTController { private final JwtService jwtService; @PostMapping("/getToken") public ResponseEntity getJwt(@RequestBody JwtRequest request) throws JoseException { return ResponseEntity.ok( JwtResponse.builder() .token(jwtService.generateToken(request.getUsername())) .build() ); } }
内容的提问来源于stack exchange,提问作者DiegoMG
相关产品推荐
相关产品推荐

