如何在.NET Core控制台应用中用Refit实现JWT认证并按Validity缓存Token
用Refit实现带Token缓存的API认证方案(.NET Core控制台应用)
刚好我之前在后台任务场景里做过类似的实现,用Refit来处理认证和Token缓存其实能做得很优雅,我给你一步步拆解具体方案:
1. 先定义认证相关的模型和Refit接口
首先把认证的请求、响应模型,以及对应的Refit接口写好,这是整个流程的基础:
// 认证请求体模型 public class AcquireTokenRequest { public string Username { get; set; } public string Password { get; set; } } // 服务器返回的认证响应模型 public class TokenResponse { public string Token { get; set; } public TimeSpan Validity { get; set; } // 假设返回的是可直接解析的时间跨度格式,比如"00:30:00" public DateTime ExpiresAt { get; set; } } // 认证接口的Refit定义 public interface IAuthApi { [Post("/server/auth/token/acquire")] Task<TokenResponse> AcquireTokenAsync([Body] AcquireTokenRequest request); }
2. 实现Token缓存与自动刷新逻辑
我们可以用.NET Core自带的IMemoryCache来缓存Token,同时根据返回的Validity值设置缓存过期时间,这样就不用手动判断Token是否失效了:
public class TokenManager { private readonly IAuthApi _authApi; private readonly IMemoryCache _cache; private readonly AcquireTokenRequest _authRequest; private const string TokenCacheKey = "ApiAuth_BearerToken"; private readonly SemaphoreSlim _semaphore = new(1, 1); // 加锁避免并发重复获取Token public TokenManager(IAuthApi authApi, IMemoryCache cache, IConfiguration config) { _authApi = authApi; _cache = cache; // 从配置文件读取账号密码,绝对不要硬编码! _authRequest = new AcquireTokenRequest { Username = config["ApiAuth:Username"], Password = config["ApiAuth:Password"] }; } public async Task<string> GetValidTokenAsync() { // 先尝试从缓存取Token if (_cache.TryGetValue(TokenCacheKey, out string cachedToken)) { return cachedToken; } // 缓存失效,加锁避免多线程重复请求Token await _semaphore.WaitAsync(); try { // 再次检查缓存,防止等待期间已经有其他线程获取了Token if (_cache.TryGetValue(TokenCacheKey, out cachedToken)) { return cachedToken; } // 调用认证接口获取新Token var tokenResponse = await _authApi.AcquireTokenAsync(_authRequest); // 根据返回的Validity设置缓存过期时间,也可以用ExpiresAt计算剩余有效期 var cacheOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(tokenResponse.Validity); // 将新Token存入缓存 _cache.Set(TokenCacheKey, tokenResponse.Token, cacheOptions); return tokenResponse.Token; } finally { _semaphore.Release(); } } }
3. 写一个自定义的请求拦截器(DelegatingHandler)
要让所有业务API请求自动带上Token,我们可以实现一个DelegatingHandler,在请求发送前自动获取有效Token并添加到请求头:
public class AuthHeaderHandler : DelegatingHandler { private readonly TokenManager _tokenManager; public AuthHeaderHandler(TokenManager tokenManager) { _tokenManager = tokenManager; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 获取有效Token var token = await _tokenManager.GetValidTokenAsync(); // 添加Bearer认证头 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); // 继续执行请求流程 return await base.SendAsync(request, cancellationToken); } }
4. 在DI容器中注册所有服务
控制台应用需要手动配置依赖注入,把我们的接口、Token管理器、拦截器都注册进去:
var builder = new HostBuilder() .ConfigureServices((hostContext, services) => { // 注册内存缓存服务 services.AddMemoryCache(); // 注册认证API的Refit客户端 services.AddRefitClient<IAuthApi>() .ConfigureHttpClient(c => c.BaseAddress = new Uri(hostContext.Configuration["ApiBaseUrl"])); // 注册Token管理器为单例 services.AddSingleton<TokenManager>(); // 注册自定义认证拦截器,再注册业务API的Refit客户端 services.AddTransient<AuthHeaderHandler>(); services.AddRefitClient<IBusinessApi>() // 这里替换成你的业务API接口 .ConfigureHttpClient(c => c.BaseAddress = new Uri(hostContext.Configuration["ApiBaseUrl"])) .AddHttpMessageHandler<AuthHeaderHandler>(); // 注册你的后台任务(用IHostedService实现) services.AddHostedService<YourBackgroundTask>(); }) .ConfigureAppConfiguration((hostingContext, config) => { // 加载配置文件,比如appsettings.json config.AddJsonFile("appsettings.json", optional: false); }); await builder.RunConsoleAsync();
5. 后台任务中直接使用业务API
最后在你的后台任务里,直接注入业务API接口即可,Token的获取和添加都是自动完成的,完全不用在业务代码里关心认证逻辑:
public class YourBackgroundTask : BackgroundService { private readonly IBusinessApi _businessApi; public YourBackgroundTask(IBusinessApi businessApi) { _businessApi = businessApi; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // 调用业务API,自动带上有效Token var taskResult = await _businessApi.RunBackgroundTaskAsync(); // 处理任务结果... await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken); } } }
额外的小提示:
- 如果服务器返回的
Validity是字符串格式(比如"30m"),需要先用TimeSpan.TryParse解析成TimeSpan类型。 - 可以结合Polly库给Token获取逻辑添加重试机制,处理网络波动导致的认证请求失败。
- 如果你的控制台应用是长期运行的,建议给缓存添加滑动过期或者监控Token过期的逻辑,确保不会出现认证失败的情况。
内容的提问来源于stack exchange,提问作者Isser Zack
相关产品推荐
相关产品推荐

