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

Razor Page中JWT结合Google认证授权问题排查(无Identity)

问题排查与解决方案

核心问题分析

你当前的代码存在两个关键问题导致401未授权:

  1. 认证Scheme配置冲突:默认认证Scheme设为JWT Bearer,但Google认证后用户身份存储在Cookie中,跳转页面时请求没有携带JWT Token,导致JWT认证失败。
  2. JWT Token未传递给前端:生成Token后仅返回页面跳转,没有将Token交付给前端,后续请求无法携带Token完成JWT认证。

针对性修改方案

方案1:切换为Cookie认证完成授权(适合Razor Pages/MVC场景)

如果是传统页面应用,直接利用Cookie存储用户身份和角色更简单,无需额外处理JWT:

1. 修改Program.cs的认证配置

将默认认证Scheme改为Cookie,同时在Google认证后自动将自定义角色添加到Cookie的Claims中:

builder.Services
.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddGoogle(options =>
{
    IConfiguration googleAuthen = builder.Configuration.GetSection("Authentication:Google");
    options.SaveTokens = true;
    options.ClientId = googleAuthen["ClientId"];
    options.ClientSecret = googleAuthen["ClientSecret"];
    options.CorrelationCookie.SameSite = SameSiteMode.Lax;
    
    // 自定义Google认证后的Claims,添加角色声明
    options.Events = new OAuthEvents
    {
        OnCreatingTicket = async context =>
        {
            var userEmail = context.Principal.FindFirstValue(ClaimTypes.Email);
            UserDTO loginedUser = context.HttpContext.RequestServices.GetRequiredService<IUserRepository>().GetUserByEmail(userEmail);
            
            // 添加角色声明到Cookie的Principal中
            var identity = (ClaimsIdentity)context.Principal.Identity;
            identity.AddClaim(new Claim(ClaimTypes.Role, "Student"));
        }
    };
});

2. 简化LoginModel代码

无需手动生成JWT,直接利用Google认证后的Cookie身份:

public IActionResult OnPostLoginByGoogle()
{
    var authProperties = new AuthenticationProperties
    {
        RedirectUri = Url.Page("/Login", pageHandler: "LoginGoogleCallback")
    };
    return new ChallengeResult(GoogleDefaults.AuthenticationScheme, authProperties);
}

public async Task<IActionResult> OnGetLoginGoogleCallbackAsync()
{
    var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    if (!authenticateResult.Succeeded)
        return BadRequest("认证失败");

    // 直接跳转,此时Cookie已包含角色声明
    return RedirectToPage("/Students/TimeTable");
}

方案2:保留JWT授权(适合API场景)

如果是前后端分离的API场景,需要将JWT返回给前端,后续请求通过Authorization: Bearer {token}头携带Token:

1. 修改LoginGoogleCallback方法,返回Token给前端

public async Task<IActionResult> OnGetLoginGoogleCallbackAsync()
{
    var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    if (!authenticateResult.Succeeded)
        return BadRequest("认证失败");

    var userEmail = authenticateResult.Principal.FindFirstValue(ClaimTypes.Email);
    UserDTO loginedUser = _userRepository.GetUserByEmail(userEmail);
    string token = CreateToken(loginedUser);

    // 示例:返回JSON格式的Token给前端
    return new JsonResult(new { Token = token });
    // 若需页面跳转,可将Token存入HttpOnly Cookie,后续页面请求时手动携带到请求头
    // Response.Cookies.Append("JwtToken", token, new CookieOptions { HttpOnly = true, Secure = true });
    // return RedirectToPage("/Students/TimeTable");
}

2. 确保前端请求携带Token

页面跳转后,需要在后续API请求中手动添加Authorization请求头:

// 示例:从Cookie获取Token并添加到请求头
const token = document.cookie.split('; ').find(row => row.startsWith('JwtToken='))?.split('=')[1];
fetch('/api/students/timetable', {
    headers: {
        'Authorization': `Bearer ${token}`
    }
});

3. 调整Program.cs的认证配置(可选)

如果同时支持Cookie和JWT认证,可配置双认证策略:

builder.Services
.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8
            .GetBytes(builder.Configuration.GetSection("AppSettings:Token").Value)),
        ValidateIssuer = false,
        ValidateAudience = false,
        // 确保角色声明被正确识别
        RoleClaimType = ClaimTypes.Role
    };
})
.AddGoogle(options =>
{
    // 原Google配置不变
    IConfiguration googleAuthen = builder.Configuration.GetSection("Authentication:Google");
    options.SaveTokens = true;
    options.ClientId = googleAuthen["ClientId"];
    options.ClientSecret = googleAuthen["ClientSecret"];
    options.CorrelationCookie.SameSite = SameSiteMode.Lax;
});

验证要点

  • 检查[Authorize(Roles = "Student")]特性所在页面/接口的请求是否携带了正确的身份凭证(Cookie或JWT Token)
  • 利用浏览器开发者工具查看请求头或Cookie,确认身份凭证存在且有效
  • 可以在授权失败的页面添加日志,打印当前用户的Claims,排查是否包含Student角色声明

内容的提问来源于stack exchange,提问作者HaNgocHieu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 16:24:58