ASP.NET MVC中如何通过重写过滤器实现应用层输出编码防XSS攻击
ASP.NET MVC 全局自动输出编码实现方案
在ASP.NET MVC中通过过滤器实现全局输出编码,优先选用IResultFilter实现,可同时覆盖视图返回、JSON返回两类常见场景,自动对所有字符串内容做HTML编码,避免XSS风险。
1. 自定义编码过滤器代码
using System.Web.Mvc; using System.Text.Encodings.Web; using System.Collections.Generic; public class AutoOutputEncodingFilter : IResultFilter { private readonly HtmlEncoder _htmlEncoder; public AutoOutputEncodingFilter() { _htmlEncoder = HtmlEncoder.Default; } public void OnResultExecuting(ResultExecutingContext filterContext) { // 跳过标记了忽略编码的控制器/方法 var skipEncoding = filterContext.ActionDescriptor.IsDefined(typeof(SkipAutoEncodingAttribute), true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(SkipAutoEncodingAttribute), true); if (skipEncoding) return; // 处理视图返回场景,编码ViewData、ViewBag中的字符串 if (filterContext.Result is ViewResult viewResult) { // 编码ViewData foreach (var key in viewResult.ViewData.Keys) { if (viewResult.ViewData[key] is string rawStr && !string.IsNullOrWhiteSpace(rawStr)) { viewResult.ViewData[key] = _htmlEncoder.Encode(rawStr); } } // 编码ViewBag var viewBagItems = viewResult.ViewBag as IDictionary<string, object>; if (viewBagItems != null) { foreach (var key in viewBagItems.Keys) { if (viewBagItems[key] is string rawStr && !string.IsNullOrWhiteSpace(rawStr)) { viewBagItems[key] = _htmlEncoder.Encode(rawStr); } } } } // 处理JSON返回场景,递归编码所有字符串字段 else if (filterContext.Result is JsonResult jsonResult && jsonResult.Data != null) { EncodeAllStringFields(jsonResult.Data, _htmlEncoder); } } public void OnResultExecuted(ResultExecutedContext filterContext) { // 无需处理结果执行后逻辑 } /// <summary> /// 递归编码对象中所有字符串类型的属性/元素 /// </summary> private void EncodeAllStringFields(object data, HtmlEncoder encoder) { if (data == null) return; var dataType = data.GetType(); // 跳过基础值类型 if (dataType.IsPrimitive || dataType == typeof(DateTime) || dataType == typeof(decimal)) return; // 直接编码字符串 if (data is string rawStr) { encoder.Encode(rawStr); return; } // 处理集合类型 if (data is System.Collections.IEnumerable collection) { foreach (var item in collection) { EncodeAllStringFields(item, encoder); } return; } // 处理自定义类的属性 var properties = dataType.GetProperties(); foreach (var prop in properties) { if (prop.PropertyType == typeof(string) && prop.CanRead && prop.CanWrite) { var propValue = prop.GetValue(data) as string; if (!string.IsNullOrWhiteSpace(propValue)) { prop.SetValue(data, encoder.Encode(propValue)); } } else if (prop.PropertyType.IsClass && prop.CanRead) { EncodeAllStringFields(prop.GetValue(data), encoder); } } } }
2. 可选:定义跳过编码的标记Attribute
针对部分不需要编码的场景,可自定义标记直接跳过过滤器逻辑:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public class SkipAutoEncodingAttribute : Attribute { }
3. 全局注册过滤器
修改App_Start/FilterConfig.cs,把自定义过滤器加入全局过滤集合,所有Action默认生效:
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); // 注册全局自动编码过滤器 filters.Add(new AutoOutputEncodingFilter()); } }
注意事项
- 可根据业务需求调整编码逻辑,比如对富文本字段单独加白名单规则,避免把合法的HTML标签也编码
- 注意避免二次编码问题,可在编码前加判断逻辑,识别已经编码过的内容直接跳过
- Razor视图默认的
@输出语法已经自带编码能力,该过滤器主要对ViewData/ViewBag动态内容、直接返回前端渲染的JSON内容做额外防护
内容的提问来源于stack exchange,提问作者shafi ahmed
相关产品推荐
相关产品推荐

