如何及何时生成新access token?Spring Boot对接API场景下的实现方案咨询
问题解答
1. 方案可行性与最佳实践判定
完全可以仅在access token过期时才生成新的access token,这属于对接OAuth2类第三方API的标准最佳实践。
该方案的优势很明确:
- 避免了高频请求token端点造成的不必要性能损耗,也降低了被第三方API限流的风险
- OAuth2协议的原生设计本身就鼓励复用有效access token,仅到期后刷新/重新申请
注意:不要完全卡着官方标注的1小时有效期才换,建议预留30秒到1分钟的缓冲时间,避免因为网络延迟、两端时钟偏差等问题导致请求携带刚过期的token被拒。
2. 具体实现方案
核心思路
用线程安全的存储容器缓存当前有效access token、刷新凭证和过期时间,请求第三方接口前先判断token是否有效,无效则用refresh token换新的token,如果refresh token也过期再走全量重新授权流程即可。
Spring Boot单机部署实现步骤
步骤1:定义线程安全的Token存储实体
public class TokenContext { private String accessToken; private LocalDateTime accessTokenExpiresAt; private String refreshToken; private LocalDateTime refreshTokenExpiresAt; // 加同步锁保证并发场景下不会重复刷新token public synchronized void updateToken(String accessToken, long accessExpiresInSec, String refreshToken, long refreshExpiresInSec) { this.accessToken = accessToken; // 预留60秒缓冲时间 this.accessTokenExpiresAt = LocalDateTime.now().plusSeconds(accessExpiresInSec - 60); this.refreshToken = refreshToken; // refresh token也预留1天缓冲 this.refreshTokenExpiresAt = LocalDateTime.now().plusSeconds(refreshExpiresInSec - 86400); } public synchronized boolean isAccessTokenValid() { return accessToken != null && LocalDateTime.now().isBefore(accessTokenExpiresAt); } public synchronized boolean isRefreshTokenValid() { return refreshToken != null && LocalDateTime.now().isBefore(refreshTokenExpiresAt); } // 省略对应getter方法 }
步骤2:注册TokenContext为单例Bean
@Configuration public class ThirdPartyApiConfig { @Bean public TokenContext tokenContext() { return new TokenContext(); } }
步骤3:封装Token刷新与获取逻辑
@Service public class ThirdPartyTokenService { @Autowired private TokenContext tokenContext; @Autowired private RestTemplate thirdPartyRestTemplate; // 对外暴露的统一获取有效token入口 public synchronized String getValidAccessToken() { if (tokenContext.isAccessTokenValid()) { return tokenContext.getAccessToken(); } // access token过期,优先用refresh token刷新 if (tokenContext.isRefreshTokenValid()) { doRefreshToken(); } else { // refresh token也过期,走全量授权流程申请新token doApplyFullToken(); } return tokenContext.getAccessToken(); } // 刷新token逻辑,根据第三方API要求调整参数 private void doRefreshToken() { MultiValueMap<String, String> reqParams = new LinkedMultiValueMap<>(); reqParams.add("grant_type", "refresh_token"); reqParams.add("refresh_token", tokenContext.getRefreshToken()); reqParams.add("client_id", "你的客户端ID"); reqParams.add("client_secret", "你的客户端密钥"); ResponseEntity<Map> resp = thirdPartyRestTemplate.postForEntity( "第三方token接口地址", reqParams, Map.class ); Map<String, Object> respData = resp.getBody(); // 更新存储的token信息 tokenContext.updateToken( respData.get("access_token").toString(), Long.parseLong(respData.get("expires_in").toString()), respData.get("refresh_token").toString(), 31 * 86400L // 按题设refresh token有效期31天计算 ); } // 全量申请新token逻辑,根据你使用的授权模式(客户端模式/授权码模式等)调整实现即可 private void doApplyFullToken() { // 实现逻辑和刷新逻辑类似,仅grant_type和请求参数不同,此处省略 } }
步骤4:调用doSomething接口时使用
@Service public class ThirdPartyApiService { @Autowired private ThirdPartyTokenService tokenService; @Autowired private RestTemplate thirdPartyRestTemplate; public void callDoSomethingEndpoint() { // 获取有效access token String accessToken = tokenService.getValidAccessToken(); // 构造请求 HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); HttpEntity<Void> reqEntity = new HttpEntity<>(headers); // 发起调用 thirdPartyRestTemplate.postForObject("第三方doSomething接口地址", reqEntity, String.class); } }
可选优化点
- 如果是分布式部署,把Token存储从内存换成Redis即可,给对应key设置和token有效期一致的过期时间
- 可以添加接口重试机制:如果携带token请求返回401状态码,直接触发一次token刷新后重试,覆盖极端时钟偏差导致的token校验失败场景
- 并发量高的场景可以用双重检查锁或者
ReentrantLock替换方法级的synchronized,优化性能
内容的提问来源于stack exchange,提问作者karan9nov
相关产品推荐
相关产品推荐

