Spring Boot应用调用外部API时如何复用未过期的access token?
Spring Boot 下AccessToken复用最优方案
1. 框架原生支持方案:Spring Security OAuth2 Client
Spring官方原生提供了OAuth2客户端能力,内置token缓存、自动刷新、过期判断逻辑,完全不需要手动实现缓存相关代码:
- 第一步引入依赖:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> </dependency>
- 第二步在配置文件中添加3个API对应的客户端配置:
spring: security: oauth2: client: registration: # 第一个API的客户端配置 api1: client-id: 替换为api1的客户端ID client-secret: 替换为api1的客户端密钥 authorization-grant-type: client_credentials scope: 替换为api1要求的权限范围 # 第二个API的客户端配置 api2: client-id: 替换为api2的客户端ID client-secret: 替换为api2的客户端密钥 authorization-grant-type: client_credentials scope: 替换为api2要求的权限范围 # 第三个API的客户端配置 api3: client-id: 替换为api3的客户端ID client-secret: 替换为api3的客户端密钥 authorization-grant-type: client_credentials scope: 替换为api3要求的权限范围 provider: api1: token-uri: 替换为api1获取token的接口地址 api2: token-uri: 替换为api2获取token的接口地址 api3: token-uri: 替换为api3获取token的接口地址
- 第三步直接注入官方工具类获取token即可,框架会自动判断过期状态,未过期直接返回缓存值,过期自动触发刷新,同时内置并发控制避免多线程同时刷新token:
@Autowired private OAuth2AuthorizedClientManager authorizedClientManager; public String getApi1Token() { OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId("api1") .principal("anonymous") .build(); OAuth2AuthorizedClient client = authorizedClientManager.authorize(request); return client.getAccessToken().getTokenValue(); }
2. 轻量优化方案(不引入Spring Security)
你当前基于volatile的实现存在高并发场景下多线程同时判断token过期、重复发起token请求的问题,可通过以下方式优化:
- 加双重检查锁避免并发刷新,同时增加提前过期逻辑避免客户端与授权服务器时间差导致的token生效问题:
private volatile String accessToken; private volatile Date expiryTime; private final Object lock = new Object(); public String getValidToken() { // 第一层判断:未过期直接返回 if (!hasExpired()) { return accessToken; } synchronized (lock) { // 第二层判断:拿到锁后再校验一次,避免前面已经有线程刷新过token if (!hasExpired()) { return accessToken; } // 调用接口获取新token TokenResponse newToken = requestNewToken(); this.accessToken = newToken.getToken(); // 提前1分钟过期,容错时间差、网络延迟问题 this.expiryTime = new Date(System.currentTimeMillis() + (newToken.getExpiresIn() - 60) * 1000); return accessToken; } } private boolean hasExpired() { if (ObjectUtils.isEmpty(this.expiryTime)) return true; return new Date().after(this.expiryTime); }
- 也可以配合Spring缓存注解+本地缓存实现,代码更简洁:
// 启动类添加@EnableCaching注解开启缓存 @Cacheable(value = "api1Token", expireAfterWrite = 29, timeUnit = TimeUnit.MINUTES) public String getApi1Token() { // 直接实现获取token的逻辑即可,缓存自动29分钟过期 return requestNewToken().getToken(); }
内容的提问来源于stack exchange,提问作者Anoop M Nair
相关产品推荐
相关产品推荐

