如何在ASP.NET Core Razor Pages中实现JWT身份验证并存储Token?
针对Razor Pages的JWT身份验证实现方案
我正尝试在Razor Pages Web应用中添加JWT身份验证,流程为:用户输入邮箱后,验证该邮箱是否存在于数据库;若存在则生成JWT Token并存储到Session或Cookie中,之后跳转到受[Authorize(Roles = "Admin")]保护的页面。
未在网络上找到针对Razor Pages的相关实现方案,尝试过教程但均未成功,现附上已编写的代码:
1. Program.cs中的身份验证配置
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(o => { o.RequireHttpsMetadata = false; o.SaveToken = true; o.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtSettings:Key"])), ValidateIssuer = true, ValidIssuer = configuration["JwtSettings:Issuer"], ValidateAudience = false, ValidateLifetime = true, }; }); services.AddAuthorization();
2. 登录按钮触发的Post方法(需在此存储Token)
public async Task<IActionResult> OnPost() { await _loginRepository.CreateToken(Email); //TODO store token here return RedirectToPage("/Picture/Index"); }
3. Token生成代码
public async Task<bool> CreateToken(string email, CancellationToken cancellationToken = default) { var journalists = await _zentralContext.v_PressInfo_Journalisten.Select(s => s.Email).ToListAsync(); if (journalists.Contains(email)) { var journalist = await _zentralContext.v_PressInfo_Journalisten.Where(w => w.Email == email).FirstAsync(); var token = CreateToken(email); //Store Token here?? return true; } else { return false; } } private string CreateToken(string email) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("TheSecretKeyNeedsToBePrettyLongSoWeNeedToAddSomeCharsHere")); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(ClaimTypes.Email, email), new Claim(ClaimTypes.Role, "Admin") }; var token = new JwtSecurityToken( issuer: "Presseinfo Extern", claims: claims, expires: DateTime.Now.AddDays(1), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }
可行实现方案
一、调整身份验证配置(Program.cs)
JWT Bearer默认适配API场景,Razor Pages属于服务器端渲染,建议结合Cookie认证维持会话,同时保留JWT的验证逻辑。修改Program.cs的认证配置:
// 优先使用Cookie认证作为默认方案 services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) // 添加JWT Bearer用于Token验证 .AddJwtBearer(o => { o.RequireHttpsMetadata = false; o.SaveToken = true; o.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtSettings:Key"])), ValidateIssuer = true, ValidIssuer = configuration["JwtSettings:Issuer"], ValidateAudience = false, ValidateLifetime = true, }; }) // 添加Cookie认证,用于存储用户身份会话 .AddCookie(options => { options.LoginPath = "/Login"; // 未授权时跳转的登录页路径 options.ExpireTimeSpan = TimeSpan.FromDays(1); // Cookie有效期与Token保持一致 }); services.AddAuthorization(); // 若选择Session存储Token,需添加Session支持 services.AddSession(options => { options.IdleTimeout = TimeSpan.FromDays(1); });
二、修改Token生成与存储逻辑
1. 调整LoginRepository的CreateToken方法,返回生成的Token
// 注入IConfiguration读取配置项 private readonly IConfiguration _configuration; public LoginRepository(ZentralContext zentralContext, IConfiguration configuration) { _zentralContext = zentralContext; _configuration = configuration; } public async Task<string> CreateToken(string email, CancellationToken cancellationToken = default) { // 简化邮箱存在性验证 var exists = await _zentralContext.v_PressInfo_Journalisten.AnyAsync(s => s.Email == email, cancellationToken); if (!exists) { return null; } return CreateToken(email); } private string CreateToken(string email) { // 从配置文件读取密钥和Issuer,与Program.cs保持一致 var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSettings:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(ClaimTypes.Email, email), new Claim(ClaimTypes.Role, "Admin") }; var token = new JwtSecurityToken( issuer: _configuration["JwtSettings:Issuer"], claims: claims, expires: DateTime.UtcNow.AddDays(1), // 使用UtcNow避免时区问题 signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }
2. 处理登录Post方法,存储Token并建立认证会话
方案A:存储Token到Cookie并建立认证会话(推荐)
直接通过Cookie认证维持用户会话,无需单独存储Token:
// 注入IConfiguration private readonly IConfiguration _configuration; private readonly ILoginRepository _loginRepository; public LoginModel(ILoginRepository loginRepository, IConfiguration configuration) { _loginRepository = loginRepository; _configuration = configuration; } public async Task<IActionResult> OnPost() { var token = await _loginRepository.CreateToken(Email); if (token == null) { ModelState.AddModelError("", "邮箱不存在"); return Page(); } // 验证Token并生成ClaimsPrincipal var tokenHandler = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSettings:Key"])), ValidateIssuer = true, ValidIssuer = _configuration["JwtSettings:Issuer"], ValidateAudience = false, ValidateLifetime = true, }; ClaimsPrincipal principal = tokenHandler.ValidateToken(token, validationParameters, out _); // 建立Cookie认证会话 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = true, // 持久化Cookie,关闭浏览器后保留 ExpiresUtc = DateTime.UtcNow.AddDays(1) }); return RedirectToPage("/Picture/Index"); }
方案B:存储Token到Session
若必须存储Token到Session,需先在Program.cs中添加app.UseSession();(放在app.UseAuthentication();之前):
public async Task<IActionResult> OnPost() { var token = await _loginRepository.CreateToken(Email); if (token == null) { ModelState.AddModelError("", "邮箱不存在"); return Page(); } // 存储Token到Session HttpContext.Session.SetString("JwtToken", token); // 验证Token生成ClaimsPrincipal,确保Authorize属性能识别用户身份 var tokenHandler = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSettings:Key"])), ValidateIssuer = true, ValidIssuer = _configuration["JwtSettings:Issuer"], ValidateAudience = false, ValidateLifetime = true, }; ClaimsPrincipal principal = tokenHandler.ValidateToken(token, validationParameters, out _); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal); return RedirectToPage("/Picture/Index"); }
三、Session存储Token的额外验证(可选)
若使用Session存储Token,可添加全局过滤器验证Token有效性:
public class JwtSessionFilter : IAsyncPageFilter { private readonly IConfiguration _configuration; public JwtSessionFilter(IConfiguration configuration) { _configuration = configuration; } public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) { var token = context.HttpContext.Session.GetString("JwtToken"); if (token != null) { var tokenHandler = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSettings:Key"])), ValidateIssuer = true, ValidIssuer = _configuration["JwtSettings:Issuer"], ValidateAudience = false, ValidateLifetime = true, }; try { ClaimsPrincipal principal = tokenHandler.ValidateToken(token, validationParameters, out _); context.HttpContext.User = principal; } catch { context.HttpContext.Session.Remove("JwtToken"); context.Result = new RedirectToPageResult("/Login"); return; } } await next(); } public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) { return Task.CompletedTask; } }
在Program.cs注册过滤器:
services.AddScoped<IAsyncPageFilter, JwtSessionFilter>();
四、确保中间件顺序正确(Program.cs)
中间件顺序会影响认证逻辑生效,需按以下顺序配置:
app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); // 若使用Session,需放在认证之前 app.UseSession(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages();
内容的提问来源于stack exchange,提问作者flomue99
相关产品推荐
相关产品推荐

