如何在HotChocolate请求中间件中修改Mutation的变量值?
在HotChocolate中批量清理Mutation输入参数的可行方案
针对你提出的需求——在请求管道中自动处理自由文本输入的XSS清理,同时避免逐个添加字段特性,这里有两个可行方案:
方案一:请求中间件中替换变量值
虽然context.Request.VariableValues是只读的,但你可以通过创建新的请求实例来替换修改后的变量集合,实现全局批量清理。
实现步骤与代码
- 自定义请求中间件,在
DocumentParserMiddleware之后执行(确保已解析变量) - 复制原只读变量字典到可修改集合
- 递归遍历所有变量值,清理字符串内容
- 创建新请求对象替换原有请求,传递给下一个中间件
public class XssCleanupMiddleware { private readonly RequestDelegate _next; public XssCleanupMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(IRequestContext context) { if (context.Request.VariableValues?.Count > 0) { // 复制只读变量到可修改字典 var modifiedVars = new Dictionary<string, object?>(context.Request.VariableValues); // 递归清理所有字符串值(含嵌套结构) foreach (var key in modifiedVars.Keys.ToList()) { modifiedVars[key] = SanitizeValue(modifiedVars[key]); } // 替换请求中的变量集合 var updatedRequest = context.Request.WithVariableValues(modifiedVars); context = context.WithRequest(updatedRequest); } await _next(context); } private object? SanitizeValue(object? value) { // 处理字符串类型 if (value is string str) { // 替换为你的XSS清理逻辑,例如使用HtmlSanitizer库 return HtmlSanitizer.Sanitize(str); } // 处理数组/集合 else if (value is IEnumerable<object> enumerable) { return enumerable.Select(SanitizeValue).ToList(); } // 处理字典类型 else if (value is IDictionary<string, object?> dict) { var cleanedDict = new Dictionary<string, object?>(); foreach (var kvp in dict) { cleanedDict[kvp.Key] = SanitizeValue(kvp.Value); } return cleanedDict; } // 处理嵌套的输入DTO对象 else if (value != null && !value.GetType().IsPrimitive && !value.GetType().IsEnum) { foreach (var prop in value.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (prop.PropertyType == typeof(string)) { var original = (string?)prop.GetValue(value); if (original != null) { prop.SetValue(value, HtmlSanitizer.Sanitize(original)); } } // 递归处理嵌套属性 else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) { var nestedVal = prop.GetValue(value); prop.SetValue(value, SanitizeValue(nestedVal)); } } return value; } return value; } } // 注册中间件到管道 builder.Services.AddGraphQLServer() // 其他GraphQL配置... .AddHttpRequestMiddleware<XssCleanupMiddleware>( builder => builder.After<DocumentParserMiddleware>() );
方案二:全局自动字段中间件(推荐)
这个方案更贴合HotChocolate的设计模式,通过类型系统过滤器自动为所有输入类型的字符串字段添加清理中间件,无需手动标记字段,彻底避免遗漏。
实现步骤与代码
- 自定义字段中间件,用于清理单个字段的输入值
- 编写类型系统过滤器,自动匹配输入类型的字符串字段并应用中间件
// 字段中间件:处理单个字段的字符串清理 public class XssCleanupFieldMiddleware { private readonly FieldDelegate _next; public XssCleanupFieldMiddleware(FieldDelegate next) { _next = next; } public async Task InvokeAsync(IMiddlewareContext context) { // 仅处理字符串类型的输入参数 if (context.ArgumentValue is string inputStr) { context.ArgumentValue = HtmlSanitizer.Sanitize(inputStr); } await _next(context); } } // 类型系统过滤器:自动为输入类型的字符串字段添加中间件 public class XssCleanupTypeFilter : ITypeSystemObjectFilter { public void Configure(ITypeSystemObjectDescriptor descriptor) { // 匹配所有输入类型的字符串字段 if (descriptor is IInputFieldDescriptor inputField && inputField.Type.IsString()) { inputField.Use<XssCleanupFieldMiddleware>(); } } } // 注册到GraphQL服务器 builder.Services.AddGraphQLServer() // 其他GraphQL配置... .AddTypeSystemObjectFilter<XssCleanupTypeFilter>();
方案对比
- 请求中间件方案:一次性处理所有变量,适合需要全局批量修改的场景,但需要自己处理各种嵌套结构,可能误处理非输入类型的变量。
- 全局字段中间件方案:更精准,仅针对输入类型的字符串字段生效,无需手动配置,完全避免遗漏,是更推荐的方案。
注意事项
- 务必使用成熟的XSS清理库(如HtmlSanitizer),不要自行编写简单的字符串替换,避免遗漏攻击向量。
- 测试嵌套输入类型、数组等复杂结构,确保清理逻辑递归生效。
- 若需排除特定字段,可在类型系统过滤器中添加自定义特性判断,例如标记
[SkipXssCleanup]的字段跳过处理。
内容的提问来源于stack exchange,提问作者KrisMale
相关产品推荐
相关产品推荐

