You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Boot端实现WSO2服务调用的OAuth2 Token自动刷新方案问询

Spring Boot实现WSO2 Token自动刷新(服务间调用场景)

我来给你详细拆解一下服务间调用WSO2时,自动刷新Token的具体实现方案,完全适配非浏览器的服务端场景~

一、核心思路梳理

咱们的核心目标是:在每次调用WSO2服务前,确保手里的access_token是有效的;如果即将过期或已过期,自动用refresh_token获取新的access_token。整体流程大概是:

  1. 首次获取初始的access_token、refresh_token和过期时间,存储起来
  2. 每次调用WSO2服务前,检查当前access_token是否有效(未过期)
  3. 若无效,调用WSO2的token刷新接口获取新的Token对
  4. 用有效的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

火山引擎 最新活动