GraphQL Hot Chocolate(Banana Cake Pop)中AccessToken未按时过期问题
问题描述
我已设置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的位置:
问题排查与修复方案
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

