如何在ASP.NET Core MVC应用中调用JWT认证的ASP.NET Core Web API
在ASP.NET Core MVC中调用JWT认证的Web API
下面是一套完整的实现方案,替代手动在Postman传令牌的方式,让MVC应用自动处理JWT令牌的获取、携带与刷新:
1. 实现JWT令牌获取逻辑
首先,MVC应用需要从你的API令牌接口获取有效令牌(通常在用户登录时执行)。示例代码如下:
// 令牌响应模型,根据你的API返回结构调整 public class TokenResponse { public string AccessToken { get; set; } public string TokenType { get; set; } public int ExpiresIn { get; set; } public string RefreshToken { get; set; } // 如果API支持刷新令牌 } // 登录时获取令牌的方法(可封装到服务类中) public async Task<TokenResponse> GetJwtTokenAsync(string username, string password) { using var client = new HttpClient(); var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://your-api-domain/auth/token"); // 按API要求的格式提交登录信息,通常是form-urlencoded var formData = new Dictionary<string, string> { ["grant_type"] = "password", ["username"] = username, ["password"] = password }; tokenRequest.Content = new FormUrlEncodedContent(formData); var response = await client.SendAsync(tokenRequest); response.EnsureSuccessStatusCode(); // 处理请求失败的情况,比如用户名密码错误 return await response.Content.ReadFromJsonAsync<TokenResponse>(); }
2. 存储令牌到Session
获取令牌后,将其存储到MVC的Session中,避免每次调用API都重新请求令牌:
// MVC登录控制器的Action [HttpPost] public async Task<IActionResult> Login(LoginViewModel model) { if (!ModelState.IsValid) return View(model); try { var tokenResponse = await GetJwtTokenAsync(model.Username, model.Password); // 将令牌存入Session HttpContext.Session.SetString("JwtAccessToken", tokenResponse.AccessToken); if (!string.IsNullOrEmpty(tokenResponse.RefreshToken)) { HttpContext.Session.SetString("JwtRefreshToken", tokenResponse.RefreshToken); } return RedirectToAction("Index", "Home"); } catch (HttpRequestException ex) { ModelState.AddModelError(string.Empty, "登录失败:" + ex.Message); return View(model); } }
注意:要启用Session,需在Program.cs中添加配置:
builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); // 设置Session超时时间 options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 在中间件管道中启用Session(放在UseRouting之后,UseAuthorization之前) app.UseSession();
3. 配置HttpClient并自动携带令牌
使用IHttpClientFactory配置可复用的HttpClient,并通过消息处理器自动添加授权头,同时处理令牌过期刷新:
3.1 实现令牌刷新消息处理器
public class TokenRefreshHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; public TokenRefreshHandler(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var httpContext = _httpContextAccessor.HttpContext; var accessToken = httpContext.Session.GetString("JwtAccessToken"); // 为请求添加Bearer授权头 if (!string.IsNullOrEmpty(accessToken)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } var response = await base.SendAsync(request, cancellationToken); // 处理401未授权,尝试刷新令牌 if (response.StatusCode == HttpStatusCode.Unauthorized) { var refreshToken = httpContext.Session.GetString("JwtRefreshToken"); if (!string.IsNullOrEmpty(refreshToken)) { var newTokenResponse = await RefreshTokenAsync(refreshToken); if (newTokenResponse != null && !string.IsNullOrEmpty(newTokenResponse.AccessToken)) { // 更新Session中的令牌 httpContext.Session.SetString("JwtAccessToken", newTokenResponse.AccessToken); if (!string.IsNullOrEmpty(newTokenResponse.RefreshToken)) { httpContext.Session.SetString("JwtRefreshToken", newTokenResponse.RefreshToken); } // 重新设置授权头并重试请求 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newTokenResponse.AccessToken); response = await base.SendAsync(request, cancellationToken); } else { // 刷新失败,清除Session并跳转登录 httpContext.Session.Remove("JwtAccessToken"); httpContext.Session.Remove("JwtRefreshToken"); } } else { // 无刷新令牌,直接跳转登录 httpContext.Session.Remove("JwtAccessToken"); } } return response; } private async Task<TokenResponse> RefreshTokenAsync(string refreshToken) { using var client = new HttpClient(); var refreshRequest = new HttpRequestMessage(HttpMethod.Post, "https://your-api-domain/auth/refresh-token"); var formData = new Dictionary<string, string> { ["grant_type"] = "refresh_token", ["refresh_token"] = refreshToken }; refreshRequest.Content = new FormUrlEncodedContent(formData); var response = await client.SendAsync(refreshRequest); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync<TokenResponse>() : null; } }
3.2 注册HttpClient与消息处理器
在Program.cs中注册相关服务:
builder.Services.AddHttpContextAccessor(); builder.Services.AddTransient<TokenRefreshHandler>(); // 注册带令牌处理的HttpClient builder.Services.AddHttpClient("ApiClient", client => { client.BaseAddress = new Uri("https://your-api-domain/"); // 设置API基础地址 }) .AddHttpMessageHandler<TokenRefreshHandler>();
4. 在MVC控制器中调用API
注入IHttpClientFactory,创建HttpClient并调用API:
public class HomeController : Controller { private readonly IHttpClientFactory _httpClientFactory; public HomeController(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task<IActionResult> ApiData() { // 检查Session中是否有令牌,无则跳转登录 if (string.IsNullOrEmpty(HttpContext.Session.GetString("JwtAccessToken"))) { return RedirectToAction("Login", "Account"); } var client = _httpClientFactory.CreateClient("ApiClient"); var response = await client.GetAsync("api/products"); // 处理请求结果,如果还是401,说明刷新也失败了,跳转登录 if (response.StatusCode == HttpStatusCode.Unauthorized) { return RedirectToAction("Login", "Account"); } response.EnsureSuccessStatusCode(); var products = await response.Content.ReadFromJsonAsync<List<Product>>(); return View(products); } }
关键注意事项
- 令牌存储:生产环境中如果需要更高安全性,可考虑将令牌加密后存储,或使用Cookie(设置HttpOnly、Secure属性)替代Session
- 错误处理:需完善各种异常情况的捕获(如网络错误、API返回非200状态码)
- 令牌过期时间:Session超时时间建议与API的AccessToken过期时间保持一致,或略长
内容的提问来源于stack exchange,提问作者nagendra babu
相关产品推荐
相关产品推荐

