ASP.NET MVC/Web API如何跨控制器设置带Token的默认请求头?
解决ASP.NET MVC中HttpClientFactory自动添加Token请求头的问题
一、选择合适的Token存储方式
对于单服务器部署场景,Session是最易上手的选择;如果是多服务器集群,推荐使用IDistributedCache(如Redis)存储Token,保证跨服务器共享。以下以Session为例讲解。
二、实现自定义DelegatingHandler
创建继承自DelegatingHandler的类,负责在每次HttpClient发送请求前,从存储中取出Token并添加到请求头:
public class TokenAuthorizationHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; public TokenAuthorizationHandler(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var httpContext = _httpContextAccessor.HttpContext; if (httpContext != null) { // 从Session中获取Token var token = httpContext.Session.GetString("AuthToken"); if (!string.IsNullOrEmpty(token)) { // 添加Bearer Token到请求头 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } } return await base.SendAsync(request, cancellationToken); } }
三、在Program.cs中配置服务
- 启用Session和HttpContextAccessor服务:
var builder = WebApplication.CreateBuilder(args); // 配置Session builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 注册HttpContextAccessor,用于在Handler中访问当前请求上下文 builder.Services.AddHttpContextAccessor(); // 注册自定义Token处理Handler builder.Services.AddTransient<TokenAuthorizationHandler>();
- 配置命名HttpClient并关联自定义Handler:
// 注册命名HttpClient,绑定API基础地址并添加Token处理逻辑 builder.Services.AddHttpClient("ProtectedApiClient", client => { client.BaseAddress = new Uri("https://your-api-base-url/"); }) .AddHttpMessageHandler<TokenAuthorizationHandler>();
- 启用Session中间件(注意顺序:放在
UseRouting之后,UseAuthorization之前):
var app = builder.Build(); // ...其他中间件配置 app.UseRouting(); // 启用Session app.UseSession(); app.UseAuthorization(); // ...其他中间件配置 app.Run();
四、登录后存储Token
在登录控制器中,调用登录API获取Token后将其存入Session:
public class AccountController : Controller { private readonly IHttpClientFactory _httpClientFactory; public AccountController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } [HttpPost] public async Task<IActionResult> Login(LoginViewModel model) { if (!ModelState.IsValid) return View(model); var client = _httpClientFactory.CreateClient("ProtectedApiClient"); var loginResponse = await client.PostAsJsonAsync("api/auth/login", model); if (loginResponse.IsSuccessStatusCode) { var tokenResult = await loginResponse.Content.ReadFromJsonAsync<TokenResponse>(); // 将Token存入Session HttpContext.Session.SetString("AuthToken", tokenResult.AccessToken); return RedirectToAction("Index", "Home"); } ModelState.AddModelError("", "登录失败,请检查用户名或密码"); return View(model); } } // 定义Token响应模型 public class TokenResponse { public string AccessToken { get; set; } public int ExpiresIn { get; set; } } // 登录视图模型 public class LoginViewModel { public string Username { get; set; } public string Password { get; set; } }
五、在其他控制器中使用带Token的HttpClient
直接注入IHttpClientFactory,创建命名客户端即可自动携带Token请求头:
public class HomeController : Controller { private readonly IHttpClientFactory _httpClientFactory; public HomeController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task<IActionResult> GetProtectedData() { var client = _httpClientFactory.CreateClient("ProtectedApiClient"); var response = await client.GetAsync("api/protected/data"); if (response.IsSuccessStatusCode) { var data = await response.Content.ReadFromJsonAsync<ProtectedData>(); return View(data); } return View("Error"); } } // 受保护数据模型 public class ProtectedData { public string Content { get; set; } }
注意事项
- 多服务器部署时,将Session替换为
IDistributedCache,注入IDistributedCache后用await _distributedCache.GetStringAsync("AuthToken")获取Token。 - 可在
TokenAuthorizationHandler中添加Token过期检测和自动刷新逻辑,避免Token失效导致请求失败。 - 生产环境建议启用HTTPS,并设置Session Cookie的
Secure属性,提升安全性。
内容的提问来源于stack exchange,提问作者Zul Huky
相关产品推荐
相关产品推荐

