You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不修改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的参数。

  1. 修改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;
    }
}
  1. 修改控制器:
@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。注意请求体只能读取一次,若控制器已读取过请求体,需先通过过滤器包装请求。

  1. 修改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();
    }
}
  1. 若控制器已读取请求体,添加过滤器包装请求:
@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()的代码不受影响。

  1. 修改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();
    }
}
  1. 修改控制器:
@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 16:15:40