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

.NET Core 6验证Google签发JWT持续返回401:validatedToken赋值合理性探讨

问题:Google JWT验证中validatedToken赋值的疑问

我正在给我的ASP.NET Core WebApi(.NET 6)配置Google签发的JWT身份验证,在Program.cs里加了自定义的GoogleTokenValidator,配置代码如下:

builder.Services.AddAuthentication(o => { 
    o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 
    o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; 
    o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 
}) 
.AddJwtBearer(o => { 
    o.IncludeErrorDetails = true; 
    o.SecurityTokenValidators.Clear(); 
    o.SecurityTokenValidators.Add(new GoogleTokenValidator()); 
    o.SaveToken = true; 
});

验证器的核心代码是这样的:

using Google.Apis.Auth; 

public class GoogleTokenValidator : ISecurityTokenValidator { 
    // ...其他接口实现代码省略
    public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) {
        var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()).Result;
        var claims = new List<Claim> { 
            new Claim(ClaimTypes.NameIdentifier, payload.Name), 
            new Claim(ClaimTypes.Name, payload.Name), 
            new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName), 
            new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName), 
            new Claim(JwtRegisteredClaimNames.Email, payload.Email), 
            new Claim(JwtRegisteredClaimNames.Sub, payload.Subject), 
            new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer), 
        };
        var principal = new ClaimsPrincipal();
        principal.AddIdentity(new ClaimsIdentity(claims, "Google"));
        // 原本这里是赋值为null,会导致401错误,现在改成了下面这行
        validatedToken = new JwtSecurityToken(securityToken); 
        // validatedToken = null; // 原代码
        return principal;
    }
}

原本把validatedToken赋值为null时,JwtBearerHandler的HandleAuthenticateAsync方法里会执行tokenValidatedContext.Properties.ExpiresUtc = GetSafeDateTime(validatedToken.ValidTo);,这会直接返回401错误。现在我改成validatedToken = new JwtSecurityToken(securityToken)是否正确?另外为什么原解答里赋值为null还能正常工作呢?

附完整的Program.cs代码:

using Core.API.Infrastructure.OAuth;
using Core.API.Infrastructure.WebApplicationBuilderServices;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Runtime.ExceptionServices;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddAuthentication(authenticationOptions => { 
    authenticationOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 
    authenticationOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; 
    authenticationOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 
}) 
.AddJwtBearer(jwtBearerOptions => { 
    jwtBearerOptions.IncludeErrorDetails = true; 
    jwtBearerOptions.SecurityTokenValidators.Clear(); 
    jwtBearerOptions.SecurityTokenValidators.Add(new GoogleTokenValidator()); 
    jwtBearerOptions.SaveToken = true; 
});

builder.Services.AddSwagger();

var app = builder.Build();

if (app.Environment.IsDevelopment()) { 
    app.UseSwagger(); 
    app.UseSwaggerUI(); 
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

解答

1. 你修改的validatedToken = new JwtSecurityToken(securityToken)是完全正确的

原因很直接:.NET 6里的JwtBearerHandler在验证通过后,会依赖validatedToken的属性(比如ValidTo)来设置认证上下文的过期时间——就是你提到的那行tokenValidatedContext.Properties.ExpiresUtc = GetSafeDateTime(validatedToken.ValidTo);。如果validatedToken是null,这里会直接抛出空引用异常,最终被框架捕获并返回401未授权错误。

你用原始JWT字符串创建JwtSecurityToken实例的做法很合理:这个对象会自动解析令牌里的所有元数据(包括过期时间、签发时间等),框架就能正常读取这些值完成后续流程,不会再触发空引用问题。

2. 原解答中赋值为null能正常工作的可能原因

我猜原解答的场景和你的存在差异,大概率是这几种情况:

  • 使用的.NET版本不同:在.NET 5及更早的版本中,JwtBearerHandler可能没有这段读取validatedToken.ValidTo的逻辑,或者对null值做了兼容处理,不会因为validatedToken为null就直接返回401。
  • 未启用SaveToken选项:你在配置里设置了o.SaveToken = true;,而原解答可能没开这个开关。当SaveToken为false时,框架可能不会去读取validatedToken的过期时间,所以null不会触发错误。
  • 原代码被简化省略:可能原作者的实际代码里有手动设置tokenValidatedContext.Properties.ExpiresUtc的逻辑,但写解答时只保留了核心验证部分,导致你直接抄过来会出问题。

额外的优化建议

其实你可以让代码更严谨一点:创建JwtSecurityToken时加上解析失败的处理,避免意外报错:

if (!JwtSecurityToken.TryParse(securityToken, out var parsedToken))
{
    throw new SecurityTokenInvalidException("Failed to parse Google JWT token");
}
validatedToken = parsedToken;

另外,用.Result同步调用异步方法GoogleJsonWebSignature.ValidateAsync可能会导致死锁,建议改成异步实现(需要实现ISecurityTokenValidator的ValidateTokenAsync方法):

public async Task<ClaimsPrincipal> ValidateTokenAsync(string securityToken, TokenValidationParameters validationParameters, CancellationToken cancellationToken)
{
    var payload = await GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings(), cancellationToken);
    
    var claims = new List<Claim> { 
        new Claim(ClaimTypes.NameIdentifier, payload.Name), 
        new Claim(ClaimTypes.Name, payload.Name), 
        new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName), 
        new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName), 
        new Claim(JwtRegisteredClaimNames.Email, payload.Email), 
        new Claim(JwtRegisteredClaimNames.Sub, payload.Subject), 
        new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer), 
    };
    
    var principal = new ClaimsPrincipal();
    principal.AddIdentity(new ClaimsIdentity(claims, "Google"));
    
    if (!JwtSecurityToken.TryParse(securityToken, out var parsedToken))
    {
        throw new SecurityTokenInvalidException("Failed to parse Google JWT token");
    }
    validatedToken = parsedToken;
    
    return principal;
}

这样既避免了同步调用异步方法的风险,也更符合ASP.NET Core的异步编程规范。

内容的提问来源于stack exchange,提问作者Francesco B.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 13:12:33