在ASP.NET Core中为Box API令牌实现缓存服务
Box API令牌缓存与自动刷新实现方案
问题背景
对接OneDrive时,MS Graph SDK内置了令牌缓存功能,通过AddInMemoryTokenCaches()即可实现令牌复用,过期时还能自动获取新令牌,无需每次请求重新获取:
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(options => { builder.Configuration.Bind("AzureAd", options); }).EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) .AddInMemoryTokenCaches();
但Box API没有这类内置功能,当前实现每次请求都会重新获取令牌(有效期60分钟),效率极低,需要实现类似的缓存与自动刷新逻辑,该方案也适用于其他无内置令牌缓存的API。
现有实现代码
服务注册(program.cs)
builder.Services.AddTransient<IBoxDrive, BoxDrive>();
接口与实现类
namespace TestProject.Repositories { public interface IBoxDrive { Task<BoxFile> FileInfoAsync(string BoxId); } public class BoxDrive : IBoxDrive { private readonly IWebHostEnvironment _env; private readonly BoxJWTAuth _session; public BoxDrive(IWebHostEnvironment env) { var _env = env; var RootPath = _env.ContentRootPath; var boxConfigLocation = Path.Combine(System.IO.Directory.GetParent(RootPath).ToString(), "box/699422622_746zo0rh_config.json"); var config = BoxConfigBuilder.CreateFromJsonString(System.IO.File.ReadAllText(boxConfigLocation)).Build(); _session = new BoxJWTAuth(config); } public async Task<BoxFile> FileInfoAsync(string BoxId) { BoxFile file = null; try { var adminToken = await _session.AdminTokenAsync(); // 有效期60分钟,应缓存复用 BoxClient adminClient = _session.AdminClient(adminToken); file = await adminClient.FilesManager.GetInformationAsync(id: BoxId); return file; } catch (Exception ex2) { return file; } } } }
解决方案
1. 调整服务生命周期
将AddTransient改为AddSingleton,确保BoxDrive实例全局唯一,令牌缓存可跨请求复用:
builder.Services.AddSingleton<IBoxDrive, BoxDrive>();
2. 实现令牌缓存与自动刷新逻辑
修改BoxDrive类,添加缓存字段、过期检查和异步线程安全控制:
namespace TestProject.Repositories { public interface IBoxDrive { Task<BoxFile> FileInfoAsync(string BoxId); } public class BoxDrive : IBoxDrive { private readonly IWebHostEnvironment _env; private readonly BoxJWTAuth _session; private string _cachedAdminToken; private DateTime _tokenExpiryTime; // 异步锁,控制并发请求下的令牌获取逻辑 private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); public BoxDrive(IWebHostEnvironment env) { _env = env; var rootPath = _env.ContentRootPath; var boxConfigLocation = Path.Combine(Directory.GetParent(rootPath).ToString(), "box/699422622_746zo0rh_config.json"); var config = BoxConfigBuilder.CreateFromJsonString(File.ReadAllText(boxConfigLocation)).Build(); _session = new BoxJWTAuth(config); } public async Task<BoxFile> FileInfoAsync(string BoxId) { BoxFile file = null; try { var adminToken = await GetCachedAdminTokenAsync(); BoxClient adminClient = _session.AdminClient(adminToken); file = await adminClient.FilesManager.GetInformationAsync(id: BoxId); return file; } catch (Exception ex) { // 建议添加日志记录异常信息,便于排查问题 return file; } } private async Task<string> GetCachedAdminTokenAsync() { // 检查缓存令牌是否有效(留5分钟缓冲,避免令牌即将过期时请求失败) if (!string.IsNullOrEmpty(_cachedAdminToken) && DateTime.UtcNow < _tokenExpiryTime) { return _cachedAdminToken; } // 异步锁防止并发请求重复获取令牌 await _tokenSemaphore.WaitAsync(); try { // 双重检查,避免等待锁的线程重复获取令牌 if (!string.IsNullOrEmpty(_cachedAdminToken) && DateTime.UtcNow < _tokenExpiryTime) { return _cachedAdminToken; } // 获取新令牌并更新缓存 var newToken = await _session.AdminTokenAsync(); _cachedAdminToken = newToken; // Box Admin Token有效期60分钟,设置过期时间为当前UTC时间加55分钟 _tokenExpiryTime = DateTime.UtcNow.AddMinutes(55); return newToken; } finally { _tokenSemaphore.Release(); } } } }
方案说明
- 令牌缓存:用
_cachedAdminToken存储有效令牌,_tokenExpiryTime记录过期时间,每次请求优先使用缓存令牌。 - 过期缓冲:设置5分钟缓冲时间,避免令牌接近过期时因网络延迟等原因导致请求失败。
- 线程安全:通过
SemaphoreSlim实现异步锁,确保同一时间只有一个请求去获取新令牌,避免资源浪费和重复请求。 - 扩展性:如果是多实例部署场景,可将内存缓存替换为分布式缓存(如Redis),实现跨实例的令牌共享。
内容的提问来源于stack exchange,提问作者Qiuzman
相关产品推荐
相关产品推荐

