Spring Boot端实现WSO2服务调用的OAuth2 Token自动刷新方案问询
Spring Boot实现WSO2 Token自动刷新(服务间调用场景)
我来给你详细拆解一下服务间调用WSO2时,自动刷新Token的具体实现方案,完全适配非浏览器的服务端场景~
一、核心思路梳理
咱们的核心目标是:在每次调用WSO2服务前,确保手里的access_token是有效的;如果即将过期或已过期,自动用refresh_token获取新的access_token。整体流程大概是:
- 首次获取初始的access_token、refresh_token和过期时间,存储起来
- 每次调用WSO2服务前,检查当前access_token是否有效(未过期)
- 若无效,调用WSO2的token刷新接口获取新的Token对
- 用有效的access_token发起业务请求
二、具体实现步骤与代码示例
1. 定义Token存储实体
首先创建一个简单的实体类来统一管理Token相关信息:
import lombok.Data; @Data public class Wso2Token { private String accessToken; private String refreshToken; // 过期时间戳(毫秒),计算方式:当前时间 + expires_in*1000 private long expireTime; }
2. 实现Token管理类(核心逻辑)
这个类负责Token的存储、过期检查和刷新操作,要重点保证线程安全,避免多线程下重复触发刷新请求:
import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; @Component public class Wso2TokenManager { // 存储当前有效Token,集群部署建议换成Redis等分布式缓存 private Wso2Token currentToken; // 锁机制,避免多线程重复刷新Token private final ReentrantLock lock = new ReentrantLock(); // WSO2配置信息,建议从application.yml读取,不要硬编码 private final String tokenUrl = "https://your-wso2-domain/oauth2/token"; private final String clientId = "your-client-id"; private final String clientSecret = "your-client-secret"; private final String initialGrantType = "password"; // 授权模式根据实际场景调整,比如client_credentials private final String username = "wso2-username"; private final String password = "wso2-password"; private final RestTemplate restTemplate; public Wso2TokenManager(RestTemplate restTemplate) { this.restTemplate = restTemplate; // 初始化时自动获取首次Token this.currentToken = fetchInitialToken(); } // 获取可用的access_token public String getValidAccessToken() { // 提前30秒检查是否即将过期,避免刚好过期的请求失败 if (isTokenExpiringSoon()) { lock.lock(); try { // 二次检查,防止等待锁的过程中已经被其他线程刷新完成 if (isTokenExpiringSoon()) { currentToken = refreshToken(); } } finally { lock.unlock(); } } return currentToken.getAccessToken(); } // 判断Token是否即将过期 private boolean isTokenExpiringSoon() { return System.currentTimeMillis() + 30000 > currentToken.getExpireTime(); } // 首次获取Token(根据你的授权模式调整参数) private Wso2Token fetchInitialToken() { Map<String, String> params = new HashMap<>(); params.put("grant_type", initialGrantType); params.put("client_id", clientId); params.put("client_secret", clientSecret); params.put("username", username); params.put("password", password); Map<String, Object> response = restTemplate.postForObject(tokenUrl, params, Map.class); Wso2Token token = new Wso2Token(); token.setAccessToken((String) response.get("access_token")); token.setRefreshToken((String) response.get("refresh_token")); long expiresIn = Long.parseLong(response.get("expires_in").toString()); token.setExpireTime(System.currentTimeMillis() + expiresIn * 1000); return token; } // 用refresh_token刷新Token private Wso2Token refreshToken() { Map<String, String> params = new HashMap<>(); params.put("grant_type", "refresh_token"); params.put("client_id", clientId); params.put("client_secret", clientSecret); params.put("refresh_token", currentToken.getRefreshToken()); Map<String, Object> response = restTemplate.postForObject(tokenUrl, params, Map.class); Wso2Token token = new Wso2Token(); token.setAccessToken((String) response.get("access_token")); // 注意:部分WSO2版本会返回新的refresh_token,部分复用旧的,根据实际返回值处理 token.setRefreshToken(response.containsKey("refresh_token") ? (String) response.get("refresh_token") : currentToken.getRefreshToken()); long expiresIn = Long.parseLong(response.get("expires_in").toString()); token.setExpireTime(System.currentTimeMillis() + expiresIn * 1000); return token; } }
3. 在业务代码中使用Token管理
在调用WSO2服务的控制器或服务类中,先获取有效Token,再携带Token发起请求:
import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class Wso2CallController { private final Wso2TokenManager tokenManager; private final RestTemplate restTemplate; public Wso2CallController(Wso2TokenManager tokenManager, RestTemplate restTemplate) { this.tokenManager = tokenManager; this.restTemplate = restTemplate; } @GetMapping("/call-wso2-service") public String callWso2Service() { // 获取有效access_token String accessToken = tokenManager.getValidAccessToken(); // 构造请求头,携带Bearer Token HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); HttpEntity<String> entity = new HttpEntity<>(headers); // 调用WSO2上的业务接口 String wso2ServiceUrl = "https://your-wso2-domain/api/your-target-service"; return restTemplate.exchange(wso2ServiceUrl, HttpMethod.GET, entity, String.class).getBody(); } }
三、关键注意事项
- 线程安全:一定要加锁或者用线程安全的存储方式,避免多线程同时触发Token刷新,造成无效请求
- 异常处理:要处理刷新Token失败的情况(比如refresh_token过期),此时可以降级到重新获取初始Token,或者抛出告警通知人工介入
- 分布式场景:如果你的Spring Boot是集群部署,建议用Redis等分布式缓存共享Token,避免每个节点各自维护Token导致不一致
- 配置化:把WSO2的域名、clientId、clientSecret等配置放到
application.yml中,方便后续维护调整 - 授权模式:示例用的是密码模式,你可以根据实际业务场景调整为客户端模式(client_credentials)或其他WSO2支持的授权模式
内容的提问来源于stack exchange,提问作者ReggieK123




