You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 06:02:35