.NET Core 3.1中缓存Auth Token的最佳实践:构造函数还是业务方法?
.NET Core 3.1中第三方服务Auth Token缓存与获取的最佳实践
问题描述
我有一个.NET Core 3.1项目,需要调用第三方服务获取数据,该服务要求请求携带Auth Token,因此必须先调用API获取Token。我希望将Token存储在MemoryCache中一段时间,避免每次调用第三方服务时都重复请求Token。现在想咨询存储与调用Auth Token端点的最佳实践:应该在服务的构造函数中获取并保存Token,还是在每个服务业务方法中调用GetAuthToken方法?
用户提供的代码示例:
public class ProductService: IProductService { private readonly ILogger _logger; private readonly IThirdService _thirdService; private readonly IMemoryCache _memoryCache; public string token; public ProductService() { //Should save token here //token = GetToken(); } private async Task<string> GetToken() { var cacheKey = "authToken"; var token = ""; if (!_memoryCache.TryGetValue(cacheKey, out token)) { token = await _thirdService.GetAuthTokenAsync(); var cacheExpiryOptions = new MemoryCacheEntryOptions { AbsoluteExpiration = DateTime.Now.AddSeconds(3350), Priority = CacheItemPriority.High, SlidingExpiration = TimeSpan.FromSeconds(3320) }; //setting cache entries _memoryCache.Set(cacheKey, token, cacheExpiryOptions); } return token; } public async Task<something> GetSomething() { //Should call Token method here or not? // } }
最佳实践方案
绝对不要在构造函数中获取Token
构造函数里做Token获取是严重错误的选择,原因如下:
- 构造函数是同步执行的,而
GetToken()是异步方法,强行同步等待会引发线程阻塞甚至死锁风险,这是.NET Core异步编程的大忌。 - 构造函数执行阶段是服务初始化环节,如果此时Token获取失败,会直接导致服务实例化失败,整个依赖链都可能崩溃,无法做到业务调用时的优雅错误处理。
- Token有过期时间,构造函数只执行一次,过期后无法自动刷新,后续所有业务请求都会携带失效Token,直接导致第三方服务调用失败。
正确做法:在业务方法中调用Token获取逻辑
在每次需要调用第三方服务的业务方法中,调用GetToken()获取最新Token,同时可以对原代码做如下优化:
public class ProductService: IProductService { private readonly ILogger<ProductService> _logger; private readonly IThirdService _thirdService; private readonly IMemoryCache _memoryCache; private const string _cacheKey = "authToken"; // 用SemaphoreSlim做异步并发控制,避免缓存失效时多请求重复调用Token接口 private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); // 依赖注入必须通过构造函数参数注入,不要留空构造函数 public ProductService(ILogger<ProductService> logger, IThirdService thirdService, IMemoryCache memoryCache) { _logger = logger; _thirdService = thirdService; _memoryCache = memoryCache; } private async Task<string> GetTokenAsync() { // 先尝试从缓存取Token if (_memoryCache.TryGetValue(_cacheKey, out string token)) { return token; } // 缓存失效时,加异步锁避免并发重复请求Token await _tokenSemaphore.WaitAsync(); try { // 双重检查,避免锁等待期间已有其他线程缓存了Token if (_memoryCache.TryGetValue(_cacheKey, out token)) { return token; } // 调用第三方接口获取Token token = await _thirdService.GetAuthTokenAsync(); // 缓存配置:过期时间比Token实际有效期短30-60秒,避免时钟偏差或网络延迟导致刚取到就过期 var cacheOptions = new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3350), Priority = CacheItemPriority.High // 滑动过期按需开启,若长时间不调用会清理Token,下次调用需重新获取 // SlidingExpiration = TimeSpan.FromSeconds(3320) }; _memoryCache.Set(_cacheKey, token, cacheOptions); _logger.LogInformation("Auth Token已缓存,过期时间:{ExpiryTime}", cacheOptions.AbsoluteExpiration); return token; } catch (Exception ex) { _logger.LogError(ex, "获取Auth Token失败"); throw; // 抛出异常让上层业务处理,或按需返回降级逻辑 } finally { _tokenSemaphore.Release(); } } public async Task<Something> GetSomethingAsync() { // 每次业务调用前获取最新Token var token = await GetTokenAsync(); // 携带Token调用第三方服务 var thirdPartyResult = await _thirdService.GetSomethingAsync(token); // 业务逻辑处理 return thirdPartyResult; } }
额外优化建议
- 封装独立Token服务:把Token获取和缓存逻辑抽成
IAuthTokenService独立服务,让多个业务服务复用逻辑,避免代码重复。 - 过期时间校准:缓存过期时间要比第三方Token的实际有效期短30-60秒,预留网络请求的时间缓冲,避免刚获取的Token就失效。
- 错误降级:如果Token获取失败,可以根据业务需求添加重试逻辑或返回友好的错误提示,不要直接抛出未处理的异常。
内容的提问来源于stack exchange,提问作者Learn AspNet
相关产品推荐
相关产品推荐

