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

GraphQL Hot Chocolate(Banana Cake Pop)中AccessToken未按时过期问题

AccessToken过期后未立即返回401的排查与修复

问题描述

我已设置ValidateLifetime: true,期望AccessToken过期后立即返回401错误,但实际该错误要过很久才会触发。在Banana Cake Pop中调用带有[HotChocolate.Authorization.Authorize]特性的请求时,指定过期token后并未出现预期的401响应。

现有代码配置

认证注册

public static class Registrar
{
    public static IServiceCollection AddJWT(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        var jwtOptionsSection = configuration.GetSection(nameof(JwtOptions));
        var jwtOptions = jwtOptionsSection.Get<JwtOptions>();

        services
            .AddAuthorization(options =>
            {
                //options.FallbackPolicy = new AuthorizationPolicyBuilder()
                //    .RequireAuthenticatedUser()
                //    .Build();
            })
            .AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = jwtOptions.Issuer,
                    ValidAudience = jwtOptions.Audience,
                    IssuerSigningKey = new SymmetricSecurityKey(JwtSecrets.IssuerSigningKey),
                };
            });

        services
            .Configure<JwtOptions>(jwtOptionsSection)
            .AddScoped<IAccessTokenService, AccessTokenService>()
            .AddScoped<IRefreshTokenService, RefreshTokenService>()
            .AddScoped<IGetAuthenticatedResultService, GetAuthenticatedResultService>();

        return services;
    }
}

创建AccessToken

internal class AccessTokenService : IAccessTokenService
{
    private readonly JwtOptions _jwtOptions;

    public AccessTokenService(IOptionsSnapshot<JwtOptions> options)
    {
        _jwtOptions = options.Value;
    }

    public string Get(IEnumerable<Claim> claims)
    {
        var securityKey = new SymmetricSecurityKey(JwtSecrets.IssuerSigningKey);

        var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            claims: claims,
            signingCredentials: signingCredentials,
            issuer: _jwtOptions.Issuer,
            audience: _jwtOptions.Audience,
            expires: DateTime.Now.AddMinutes(_jwtOptions.AccessTokenExpiryMinutes)); // 此处存在时区问题

        var tokenString = new JwtSecurityTokenHandler().WriteToken(token);

        return tokenString;
    }
}

查询定义

[ExtendObjectType(typeof(Query))]
public class UserQuery
{
    [Authorize] // 使用HotChocolate.Authorization;
    [UseOffsetPaging(IncludeTotalCount = true, DefaultPageSize = 20)]
    [UseFiltering]
    [UseSorting]
    public IQueryable<User> GetUsers(IDbRepository<User> users) => users.Get();
}

GraphQL注册

public static class Registrar
{
    public static IRequestExecutorBuilder AddGraphQL(this IServiceCollection services) => services
        .AddGraphQLServer()
        .AddAuthorization()
        .AddFiltering()
        .AddSorting()
        .AddErrorFilter<ErrorFilter>()
        .AddQueryType<Query>()
        .AddMutationType<Mutation>().AddMutationConventions()
        ...
        ;
}

Banana Cake Pop中指定token的位置:
Banana Cake Pop中指定token的位置

问题排查与修复方案

1. 修复Token过期时间的时区问题

JWT标准中,过期时间(exp)采用UTC时间,但你创建token时用了DateTime.Now(本地时间)。如果服务器时区与本地时区不一致,会导致实际过期时间被错误计算,出现“过期很久才触发401”的现象。

修改创建Token的代码:
将DateTime.Now替换为DateTime.UtcNow:

var token = new JwtSecurityToken(
    claims: claims,
    signingCredentials: signingCredentials,
    issuer: _jwtOptions.Issuer,
    audience: _jwtOptions.Audience,
    expires: DateTime.UtcNow.AddMinutes(_jwtOptions.AccessTokenExpiryMinutes));

2. 关闭Token验证的时间偏差容忍

默认情况下,TokenValidationParameters的ClockSkew属性值为5分钟,即允许token过期时间有5分钟的偏差。如果需要严格的过期验证,需将其设置为TimeSpan.Zero。

修改认证注册代码:
在TokenValidationParameters中添加ClockSkew配置:

options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = true,
    ValidateAudience = true,
    ValidateLifetime = true,
    ValidateIssuerSigningKey = true,
    ValidIssuer = jwtOptions.Issuer,
    ValidAudience = jwtOptions.Audience,
    IssuerSigningKey = new SymmetricSecurityKey(JwtSecrets.IssuerSigningKey),
    ClockSkew = TimeSpan.Zero // 禁用时间偏差容忍
};

3. 检查ErrorFilter是否拦截了401错误

你的GraphQL配置中添加了AddErrorFilter<ErrorFilter>,如果这个过滤器将认证相关的错误(如401)修改或隐藏,会导致前端无法收到预期的401响应。需检查ErrorFilter的实现,确保它没有拦截或篡改认证错误。

4. 确认GraphQL授权与JWT认证的集成

确保HotChocolate的授权系统正确使用了JWT认证方案。可以在AddAuthorization中明确指定认证方案:

.AddAuthorization(options =>
{
    options.AddPolicy("Default", policy =>
    {
        policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
        policy.RequireAuthenticatedUser();
    });
})

然后在[Authorize]特性中指定策略:[Authorize(Policy = "Default")]

验证方案

修改完成后,重新生成过期时间较短的token,在Banana Cake Pop中调用接口,确认过期后立即返回401错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 01:14:54