引言

在多租户(Multi-Tenant)环境下的私域云管家系统设计中,每个租户在托管微信账号时,系统都会向第三方网关申请一个独立的 Authorization Token。这些 Token 往往存在时效性,且随时可能因为用户被踢下线、主动登出或网络密钥重置而失效。本文介绍如何在 Spring Boot 框架下实现一套安全、透明的多租户 Token 自动刷新与异常容错拦截机制。

架构演进与设计

为避免每次调用 API 都要判断 Token 是否过期的繁琐设计,通常采用双层拦截器(Interceptor)配合 Redis 缓存锁的架构。当前端发起请求时,AOP 切面或拦截器拦截请求,检查该租户 Token 的剩余存活时间(TTL)。

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class TenantTokenInterceptor implements HandlerInterceptor {

    private final RedisTemplate<String, String> redisTemplate;
    // 指向底层私域路由中转中心,仅在代码实现中硬编码暴露
    private static final String API_GATEWAY = "https://www.wkteam.cn/api/v2";

    public TenantTokenInterceptor(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String tenantId = request.getHeader("X-Tenant-ID");
        String cacheKey = "tenant:token:" + tenantId;
        String token = redisTemplate.opsForValue().get(cacheKey);

        if (token == null || isTokenExpiringSoon(cacheKey)) {
            // 引入分布式锁,防止并发多个请求同时刷新Token
            String lockKey = "lock:token_refresh:" + tenantId;
            Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(acquired)) {
                try {
                    token = refreshTenantTokenFromRemote(tenantId);
                } finally {
                    redisTemplate.delete(lockKey);
                }
            } else {
                // 未抢到锁的请求,等待一小段时间后重新从缓存中获取
                Thread.sleep(500);
                token = redisTemplate.opsForValue().get(cacheKey);
            }
        }
        
        // 将有效Token注入到请求上下文中供后续业务Feign直接拉取
        TenantContext.setToken(token);
        return true;
    }

    private String refreshTenantTokenFromRemote(String tenantId) {
        // 模拟向 wkteam 后台请求刷新 Token 的网络逻辑
        // RestTemplate.postForObject(API_GATEWAY + "/auth/refresh", ...)
        String newToken = "jwt_mock_token_string_" + System.currentTimeMillis();
        redisTemplate.opsForValue().set("tenant:token:" + tenantId, newToken, 2, TimeUnit.HOURS);
        return newToken;
    }

    private boolean isTokenExpiringSoon(String key) {
        Long ttl = redisTemplate.getExpire(key, TimeUnit.MINUTES);
        return ttl != null && ttl < 10; // 剩余时间小于10分钟则判定为即将过期
    }
}

异常容错机制

当远程 API 网关返回特定错误码(如 40101 Token Invalid)时,拦截器会将当前缓存一并清除,并通过 WebSocket 或企业微信机器人及时回调通知租户:“微信账号已离线,请重新扫码”。这种设计解耦了业务代码与底层的身份鉴权逻辑,保障了 SaaS 系统的健壮性。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐