线程休眠30分钟后调用JWT工具类触发IllegalStateException问题
问题解决:休眠后获取JWT令牌触发IllegalStateException
核心原因
- 请求上下文已销毁:Spring的
RequestContextHolder基于ThreadLocal存储请求属性,Web容器会在请求处理完成后立即清理这些属性。30分钟的休眠远超正常请求生命周期,此时原请求上下文早已被销毁,线程甚至可能被容器回收复用,调用currentRequestAttributes()自然会抛出无线程绑定请求的异常。 - 工具类职责错误:你当前的
JwtTokenUtils.getToken()方法是从请求头提取已存在的令牌,而非生成新的JWT令牌,完全不符合“生成新令牌调用API”的需求。
解决方案
1. 修正JWT工具类,添加令牌生成能力
重构工具类,明确区分“提取请求中令牌”和“生成新令牌”的职责:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Base64; public class JwtTokenUtils { private static final Logger log = LoggerFactory.getLogger(JwtTokenUtils.class); @Value("${security.jwt.token.secret-key:secret-key}") private String secretKey; @Value("${security.jwt.token.expire-length:3600000}") // 默认1小时过期,可配置 private long validityInMilliseconds; @PostConstruct protected void init() { secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); } // 生成新的JWT令牌(核心方法) public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put("roles", userDetails.getAuthorities()); return createToken(claims, userDetails.getUsername()); } private String createToken(Map<String, Object> claims, String subject) { Date now = new Date(); Date validity = new Date(now.getTime() + validityInMilliseconds); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(now) .setExpiration(validity) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } // 保留原功能:从当前请求提取已有的令牌(添加异常捕获) public String extractTokenFromRequest() { String authToken = getAuthorizationHeader(); if (authToken != null && authToken.startsWith("Bearer ")) { return authToken.substring(7); } return null; } private String getAuthorizationHeader() { try { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = attributes.getRequest(); return request != null ? request.getHeader("Authorization") : null; } catch (IllegalStateException e) { log.warn("无线程绑定请求,无法提取Authorization头", e); return null; } } }
2. 用异步任务处理延迟操作(推荐)
直接在请求线程中休眠30分钟会严重占用Tomcat线程池资源,导致服务性能下降。改用Spring异步任务提前保存用户信息,在独立线程中完成延迟操作:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import java.util.concurrent.TimeUnit; @Service public class DelayedApiService { private static final Logger log = LoggerFactory.getLogger(DelayedApiService.class); private final JwtTokenUtils jwtTokenUtils; private final RestTemplate restTemplate; private final UserDetailsService userDetailsService; // 构造注入(替代@Autowired) public DelayedApiService(JwtTokenUtils jwtTokenUtils, RestTemplate restTemplate, UserDetailsService userDetailsService) { this.jwtTokenUtils = jwtTokenUtils; this.restTemplate = restTemplate; this.userDetailsService = userDetailsService; } @Async public void executeAfterDelay(String username) { try { TimeUnit.MINUTES.sleep(30); // 根据用户名加载用户信息,生成新令牌 UserDetails userDetails = userDetailsService.loadUserByUsername(username); String newToken = jwtTokenUtils.generateToken(userDetails); String accessToken = "Bearer " + newToken; // 调用目标API HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", accessToken); HttpEntity<String> entity = new HttpEntity<>(headers); ResponseEntity<String> response = restTemplate.exchange("https://your-target-api-url", HttpMethod.GET, entity, String.class); // 处理API响应 log.debug("API调用结果: {}", response.getBody()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("延迟任务被中断", e); } } // 在请求处理方法中调用异步任务 public void handleOriginalRequest() { // 从当前Security上下文获取用户名 String username = ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername(); // 触发异步延迟任务,立即返回响应给客户端 executeAfterDelay(username); } }
3. 若必须在原线程处理(不推荐)
如果业务限制必须在原线程执行,需提前保存用户信息,避免依赖已销毁的请求上下文:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import java.time.Instant; import java.util.concurrent.TimeUnit; public class OriginalThreadHandler { private static final Logger log = LoggerFactory.getLogger(OriginalThreadHandler.class); private final JwtTokenUtils jwtTokenUtils; private final UserDetailsService userDetailsService; public OriginalThreadHandler(JwtTokenUtils jwtTokenUtils, UserDetailsService userDetailsService) { this.jwtTokenUtils = jwtTokenUtils; this.userDetailsService = userDetailsService; } public void process() throws InterruptedException { log.debug("Reached here ======================== {}", Instant.now()); // 休眠前,从当前Security上下文获取并保存用户信息 UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); String username = userDetails.getUsername(); TimeUnit.MINUTES.sleep(30); log.error("again starting {}", Instant.now()); // 使用保存的用户名生成新令牌 UserDetails newUserDetails = userDetailsService.loadUserByUsername(username); String newToken = jwtTokenUtils.generateToken(newUserDetails); String accessToken = "Bearer " + newToken; // 调用API... } }
内容的提问来源于stack exchange,提问作者Shalini Gupta
相关产品推荐
相关产品推荐

