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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 21:05:17