Azure Function实现未过期访问令牌在后续API调用中的复用
复用外部API访问令牌的解决方案
核心思路是缓存令牌并验证过期状态,同时保证并发场景下的线程安全,避免重复获取令牌。以下是具体实现步骤和代码示例:
1. 定义令牌相关模型
首先创建存储令牌和过期时间的模型,以及外部API返回的令牌响应模型:
// 缓存用的令牌模型 public class CachedAccessToken { public string Token { get; set; } public DateTime ExpiresAt { get; set; } } // 外部API返回的令牌响应 public class ExternalTokenResponse { public string AccessToken { get; set; } public int ExpiresIn { get; set; } // 令牌有效期(秒) }
2. 实现带缓存的令牌获取逻辑
在HTTP Trigger类中,使用静态变量存储缓存令牌,并用SemaphoreSlim处理异步并发场景,防止多个请求同时触发令牌获取:
public class MyHttpTrigger { // 静态变量存储缓存令牌,仅在当前实例有效 private static CachedAccessToken _cachedToken; // 异步锁,控制并发令牌获取 private static readonly SemaphoreSlim _tokenLock = new SemaphoreSlim(1, 1); [FunctionName("MyHttpTrigger")] public async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { // 获取有效令牌 var validToken = await GetValidAccessTokenAsync(log); // 调用外部API var apiResult = await PerformExternalAPICallAsync(validToken, log); return new OkObjectResult(apiResult); } private async Task<string> GetValidAccessTokenAsync(ILogger log) { // 先检查缓存令牌是否有效(提前30秒过期,避免网络延迟导致令牌失效) if (_cachedToken != null && _cachedToken.ExpiresAt > DateTime.UtcNow.AddSeconds(30)) { log.LogInformation("使用缓存的访问令牌"); return _cachedToken.Token; } // 进入异步锁,防止并发请求重复获取令牌 await _tokenLock.WaitAsync(); try { // 双重检查:避免等待锁期间已有其他请求更新了令牌 if (_cachedToken != null && _cachedToken.ExpiresAt > DateTime.UtcNow.AddSeconds(30)) { log.LogInformation("锁检查后使用缓存的访问令牌"); return _cachedToken.Token; } // 调用外部API获取新令牌 log.LogInformation("获取新的访问令牌"); var tokenResponse = await FetchAccessTokenFromExternalAPIAsync(log); // 更新缓存令牌,提前30秒设置过期时间 _cachedToken = new CachedAccessToken { Token = tokenResponse.AccessToken, ExpiresAt = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn - 30) }; return _cachedToken.Token; } finally { // 释放锁 _tokenLock.Release(); } } // 调用外部API获取令牌的具体实现 private async Task<ExternalTokenResponse> FetchAccessTokenFromExternalAPIAsync(ILogger log) { using var client = new HttpClient(); var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://external-api.com/oauth/token"); // 客户端凭据流参数 var formData = new Dictionary<string, string> { {"grant_type", "client_credentials"}, {"client_id", "你的客户端ID"}, {"client_secret", "你的客户端密钥"}, {"scope", "所需权限范围"} }; tokenRequest.Content = new FormUrlEncodedContent(formData); var response = await client.SendAsync(tokenRequest); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync<ExternalTokenResponse>(); } // 调用外部业务API的具体实现 private async Task<string> PerformExternalAPICallAsync(string token, ILogger log) { using var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var apiResponse = await client.GetAsync("https://external-api.com/api/目标资源"); apiResponse.EnsureSuccessStatusCode(); return await apiResponse.Content.ReadAsStringAsync(); } }
3. 关键注意事项
- 线程安全:使用
SemaphoreSlim替代lock,适配异步场景,避免阻塞线程池线程 - 缓冲时间:提前30秒标记令牌过期,防止网络延迟导致令牌在请求过程中失效
- 多实例部署:如果函数是多实例运行(如Azure Functions消耗计划),静态变量仅在单个实例内有效,此时需要改用分布式缓存(如Redis)共享令牌,所有实例统一从分布式缓存读取和更新
- 失效重试:如果调用外部API返回
401 Unauthorized,即使缓存令牌未过期,也应主动刷新令牌并重试请求(可添加Polly等重试库实现)
内容的提问来源于stack exchange,提问作者khert
相关产品推荐
相关产品推荐

