如何通过外部JWT认证保护ASP.NET Core 6 MVC应用的视图?
解决方案步骤
1. 处理登录逻辑
先创建登录视图模型,接收表单输入:
public class LoginViewModel { [Required] public string Username { get; set; } [Required] [DataType(DataType.Password)] public string Password { get; set; } }
在AccountController中实现登录动作,调用外部API获取JWT并完成用户认证:
public class AccountController : Controller { private readonly HttpClient _httpClient; public AccountController(HttpClient httpClient) { _httpClient = httpClient; } [HttpGet] public IActionResult Login() => View(); [HttpPost] public async Task<IActionResult> Login(LoginViewModel model) { if (!ModelState.IsValid) return View(model); // 调用外部登录API提交凭证 var loginPayload = new { Username = model.Username, Password = model.Password }; var response = await _httpClient.PostAsJsonAsync("https://external-api-domain/login", loginPayload); if (!response.IsSuccessStatusCode) { ModelState.AddModelError(string.Empty, "登录失败,请检查用户名或密码"); return View(model); } var jwtToken = await response.Content.ReadAsStringAsync(); // 解析JWT获取用户声明,用于Cookie认证 var jwtHandler = new JwtSecurityTokenHandler(); var jwtTokenObj = jwtHandler.ReadJwtToken(jwtToken); var claims = jwtTokenObj.Claims.ToList(); // 将原始JWT存入声明,方便后续调用数据API claims.Add(new Claim("JwtToken", jwtToken)); var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProps = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = jwtTokenObj.ValidTo }; // 生成认证Cookie,完成用户登录 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), authProps); return RedirectToAction("Index", "Home"); } [HttpPost] public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Login"); } }
2. 配置认证与服务
在Program.cs中配置Cookie认证和HttpClient服务:
var builder = WebApplication.CreateBuilder(args); // 注册HttpClient,用于调用外部API builder.Services.AddHttpClient(); // 添加MVC服务 builder.Services.AddControllersWithViews(); // 配置Cookie认证 builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Account/Login"; // 未认证用户跳转路径 options.LogoutPath = "/Account/Logout"; options.ExpireTimeSpan = TimeSpan.FromHours(1); options.SlidingExpiration = true; options.Cookie.HttpOnly = true; // 禁止前端JS读取,防XSS options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 生产环境强制HTTPS }); var app = builder.Build(); // 中间件顺序不可乱 app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); // 先执行认证,再执行授权 app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
3. 页面授权与数据API调用
用[Authorize]标记需要认证的控制器/动作,在业务逻辑中取出JWT调用外部数据API:
[Authorize] public class HomeController : Controller { private readonly HttpClient _httpClient; public HomeController(HttpClient httpClient) { _httpClient = httpClient; } public async Task<IActionResult> Index() { // 从用户声明中获取存储的JWT var jwtToken = User.FindFirstValue("JwtToken"); if (string.IsNullOrEmpty(jwtToken)) return RedirectToAction("Login", "Account"); // 给请求添加Bearer令牌,调用外部数据API _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken); var data = await _httpClient.GetFromJsonAsync<YourDataModel>("https://external-api-domain/data"); return View(data); } }
4. 登录视图(Login.cshtml)
创建基础登录表单:
@model LoginViewModel <h1>用户登录</h1> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <form method="post"> <div class="form-group"> <label asp-for="Username"></label> <input asp-for="Username" class="form-control" /> <span asp-validation-for="Username" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <button type="submit" class="btn btn-primary">登录</button> </form>
关键注意事项
- JWT存储安全:将JWT存入HttpOnly Cookie或用户声明,避免前端直接操作,降低XSS风险。
- 令牌过期处理:实际项目中需捕获令牌过期的API返回,自动跳转到登录页重新获取令牌。
- 生产环境配置:确保外部API和自身站点都使用HTTPS,防止令牌在传输过程中被窃取。
内容的提问来源于stack exchange,提问作者A Gilani
相关产品推荐
相关产品推荐

