如何在Hot Chocolate GraphQL中用HttpOnly Cookie处理刷新令牌并配置端点?
基于Hot Chocolate的GraphQL令牌刷新方案(ASP.NET Core 6)
方案1:配置独立的GraphQL刷新端点(/graphql/refreshtoken)
Hot Chocolate支持多Schema多端点配置,完全可以单独创建一个仅处理令牌刷新的GraphQL端点,无需依赖REST。
步骤1:注册多GraphQL服务
在Program.cs中,除了主GraphQL服务,额外注册一个专门用于刷新令牌的服务,并绑定到指定端点:
// 主GraphQL服务(处理业务查询/突变) builder.Services .AddGraphQLServer() .AddQueryType<Query>() .AddMutationType<BusinessMutation>() .AddAuthorization() .RegisterDbContext<AppDbContext>(); // 令牌刷新专用GraphQL服务 builder.Services .AddGraphQLServer("RefreshTokenSchema") .AddMutationType<RefreshTokenMutation>() // 仅包含刷新令牌的突变 .AddAuthorization() .ModifyRequestOptions(opt => { // 限制该端点仅接受Mutation操作 opt.AllowedOperations = new[] { OperationType.Mutation }; }); // 映射端点 app.MapGraphQL("/graphql"); // 主业务端点 app.MapGraphQL("/graphql/refreshtoken", schemaName: "RefreshTokenSchema"); // 刷新令牌端点
步骤2:实现刷新令牌突变
创建仅处理刷新逻辑的RefreshTokenMutation,从Cookie中读取刷新令牌,验证后生成新令牌并更新Cookie:
public class RefreshTokenMutation { public async Task<RefreshTokenResponse> RefreshToken( [FromServices] ITokenGenerator tokenGenerator, [FromServices] IRefreshTokenValidator tokenValidator, [FromServices] IHttpContextAccessor httpContextAccessor, CancellationToken ct) { var httpContext = httpContextAccessor.HttpContext; // 从Cookie获取刷新令牌(路径匹配/graphql/refreshtoken,浏览器自动携带) if (!httpContext.Request.Cookies.TryGetValue("RefreshToken", out var refreshToken)) { throw new GraphQLException(new Error("刷新令牌不存在", "TOKEN_MISSING")); } // 验证刷新令牌有效性,获取用户信息 var validUser = await tokenValidator.ValidateAndGetUserAsync(refreshToken, ct); if (validUser == null) { throw new GraphQLException(new Error("刷新令牌无效或已过期", "TOKEN_INVALID")); } // 生成新的访问令牌和刷新令牌 var newAccessToken = tokenGenerator.CreateAccessToken(validUser); var newRefreshToken = tokenGenerator.CreateRefreshToken(validUser); // 更新访问令牌Cookie(路径/,所有请求携带) httpContext.Response.Cookies.Append("AccessToken", newAccessToken, new CookieOptions { HttpOnly = true, Path = "/", Secure = builder.Environment.IsProduction(), // 生产环境启用HTTPS SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddMinutes(15) }); // 更新刷新令牌Cookie(路径/graphql/refreshtoken,仅请求该端点时携带) httpContext.Response.Cookies.Append("RefreshToken", newRefreshToken, new CookieOptions { HttpOnly = true, Path = "/graphql/refreshtoken", Secure = builder.Environment.IsProduction(), SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(7) }); return new RefreshTokenResponse { Success = true }; } } // 响应DTO public record RefreshTokenResponse(bool Success);
方案2:通过主GraphQL端点实现令牌刷新(无需独立端点)
如果不想维护多端点,可以在主GraphQL服务中添加刷新令牌的突变,只需调整刷新令牌的Cookie路径为/graphql,确保请求主端点时浏览器会携带该Cookie。
实现刷新突变
在主业务Mutation类中添加刷新逻辑:
public class BusinessMutation { // 其他业务突变... public async Task<RefreshTokenResponse> RefreshToken( [FromServices] ITokenGenerator tokenGenerator, [FromServices] IRefreshTokenValidator tokenValidator, [FromServices] IHttpContextAccessor httpContextAccessor, CancellationToken ct) { var httpContext = httpContextAccessor.HttpContext; // 从Cookie获取刷新令牌(路径改为/graphql,主端点请求时携带) if (!httpContext.Request.Cookies.TryGetValue("RefreshToken", out var refreshToken)) { throw new GraphQLException(new Error("刷新令牌不存在", "TOKEN_MISSING")); } var validUser = await tokenValidator.ValidateAndGetUserAsync(refreshToken, ct); if (validUser == null) { throw new GraphQLException(new Error("刷新令牌无效或已过期", "TOKEN_INVALID")); } var newAccessToken = tokenGenerator.CreateAccessToken(validUser); var newRefreshToken = tokenGenerator.CreateRefreshToken(validUser); // 更新访问令牌Cookie httpContext.Response.Cookies.Append("AccessToken", newAccessToken, new CookieOptions { HttpOnly = true, Path = "/", Secure = builder.Environment.IsProduction(), SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddMinutes(15) }); // 更新刷新令牌Cookie(路径改为/graphql) httpContext.Response.Cookies.Append("RefreshToken", newRefreshToken, new CookieOptions { HttpOnly = true, Path = "/graphql", Secure = builder.Environment.IsProduction(), SameSite = SameSiteMode.Strict, Expires = DateTimeOffset.UtcNow.AddDays(7) }); return new RefreshTokenResponse { Success = true }; } }
最佳实践
- Cookie安全配置:始终为令牌Cookie设置
HttpOnly(防止XSS)、Secure(仅HTTPS传输)、SameSite=Strict(防止CSRF)属性。 - 令牌生命周期管理:访问令牌有效期短(15-30分钟),刷新令牌有效期长(7-30天),每次刷新时生成新的刷新令牌并废弃旧令牌。
- 刷新令牌持久化:将刷新令牌存储在数据库中,关联用户ID和过期时间,验证时检查是否存在且未被吊销。
- 端点权限控制:对于独立刷新端点,限制仅接受POST请求,且仅暴露刷新突变;对于主端点的刷新突变,可添加授权策略确保仅能通过无效访问令牌的请求触发。
- 错误处理:明确返回令牌缺失、无效、过期等错误信息,前端根据错误类型引导用户重新登录。
- 生产环境配置:强制启用HTTPS,关闭开发环境的不安全配置,使用分布式缓存存储令牌黑名单(如吊销的刷新令牌)。
内容的提问来源于stack exchange,提问作者Elshad Shabanov
相关产品推荐
相关产品推荐

