如果您希望看到关键字过滤算法的话那么可能就要失望了。博客园中已经有不少关于此类算法的文章(例如这里和这里),虽然可能无法直接满足特定需求,但是已经足够作为参考使用。而本文的目的,是给出一个较为完整的关键字过滤功能,也就是将用户输入中的敏感字符进行替换——这两者有什么区别?那么就请继续看下去吧。:)

有趣的需求

关键字过滤功能自然无比重要,但是如果要在代码中对每个输入进行检查和替换则会是一件非常费神费事的事情。尤其是如果网站已经有了一定规模,用户输入功能已经遍及各处,而急需对所有输入进行关键字过滤时,上述做法更可谓“远水解不了近渴”。这时候,如果有一个通用的办法,呼得一下为整站的输入加上了一道屏障,那该是一件多么惬意的事情。这就是本文希望解决的问题。是不是很简单?我一开始也这么认为,不过事实上并非那么一帆风顺,而且在某些特定条件下似乎更是没有太好的解决方法……

您慢坐,且听我慢慢道来……

实现似乎很简单

数据结构中的单向链表可谓无比经典。有人说:单向链表的题目好难啊,没法逆序查找,很多东西都不容易做。有人却说:单向链表既然只能向一个方向遍历,那么变化就会很有限,所以题目不会过于复杂。老赵觉得后者的说法不无道理。例如在现在的问题上,我们如果要在一个ASP.NET应用程序中做一个统一的“整站方案”,HttpModule似乎是唯一的选择。

思路如下:我们在Request Pipeline中最早的阶段(BeginRequest)将请求的QueryString和Form集合中的值做过滤,则接下来的ASP.NET处理过程中一切都为“规范”的文字了。说干就干,不就是替换两个NameValueCollection对象中的值吗?这再简单不过了:

public class FilterForbiddenWordModule : IHttpModule
{void IHttpModule.Dispose() { }void IHttpModule.Init(HttpApplication context){context.BeginRequest += new EventHandler(OnBeginRequest);}private static void OnBeginRequest(object sender, EventArgs e){var request = (sender as HttpApplication).Request;ProcessCollection(request.QueryString);ProcessCollection(request.Form);}private static void ProcessCollection(NameValueCollection collection){var copy = new NameValueCollection();foreach (string key in collection.AllKeys){Array.ForEach(collection.GetValues(key),v => copy.Add(key, ForbiddenWord.Filter(v)));}collection.Clear();collection.Add(copy);}
}

在BeginRequest阶段,我们将调用ProcessCollection将QueryString和Form两个NameValueCollection中的值使用ForbiddenWord.Filter方法进行处理。ForbiddenWord是一个静态类,其中的Filter方法会将原始字符串中的敏感字符使用“**”进行替换。替换方法不在本文的讨论范围内,因此我们就以如下方式进行简单替换:

public static class ForbiddenWord
{public static string Filter(string original){return original.Replace("FORBIDDEN_WORD", "**");}
}

看似没有问题,OK,随便打开一张页面看看……

Collection is read-only.Description: An unhandled exception occurred during the execution of the current web request...
Exception Details: System.NotSupportedException: Collection is read-only.

呀,只读……这是怎么回事?不就是一个NameValueCollection吗?在不得不请出.NET Reflector之后,老赵果然发现其中有猫腻……

public class HttpRequest
{ ...public NameValueCollection Form{get{if (this._form == null){this._form = new HttpValueCollection();if (this._wr != null){this.FillInFormCollection();}this._form.MakeReadOnly();}if (this._flags[2]){this._flags.Clear(2);ValidateNameValueCollection(this._form, "Request.Form");}return this._form;}}...
}

虽然HttpRequest.Form属性为NameValueCollection类型,但是其中的_form变量事实上是一个HttpValueCollection对象。而HttpValueCollection自然是NameValueCollection的子类,而造成其“只读”的最大原因便是:

[Serializable]
internal class HttpValueCollection : NameValueCollection
{ ...internal void MakeReadOnly(){base.IsReadOnly = true;} ...
}

IsReadOnly是定义在NameValueCollection基类NameObjectCollectionBase上的protected属性,这意味着如果我们只有编写一个如同NameValueCollection或HttpValueCollection般的子类才能直接访问它,而现在……反射吧,兄弟们。

public class FilterForbiddenWordModule : IHttpModule
{private static PropertyInfo s_isReadOnlyPropertyInfo;static FilterForbiddenWordModule(){Type type = typeof(NameObjectCollectionBase);s_isReadOnlyPropertyInfo = type.GetProperty("IsReadOnly",BindingFlags.Instance | BindingFlags.NonPublic);}...private static void ProcessCollection(NameValueCollection collection){var copy = new NameValueCollection();foreach (string key in collection.AllKeys){Array.ForEach(collection.GetValues(key),v => copy.Add(key, ForbiddenWord.Filter(v)));}// set readonly to false.s_isReadOnlyPropertyInfo.SetValue(collection, false, null);collection.Clear();collection.Add(copy);// set readonly to true.s_isReadOnlyPropertyInfo.SetValue(collection, true, null);}
}

现在再打开个页面看看,似乎没事。那么就来体验一下这个HttpModule的功效吧。我们先准备一个空的aspx页面,加上以下代码:

<form id="form1" runat="server"><asp:TextBox runat="server" TextMode="MultiLine" /><asp:Button runat="server" Text="Click" />
</form>

打开页面,在文本框内填写一些敏感字符并点击按钮:

嗨,效果似乎还不错!

问题来了

太简单了,是不?

可惜问题才刚开始:如果业务中有些字段不应该被替换怎么办?例如“密码”。如果我们只做到现在这点,那么密码“let-us-say-shit”和“let-us-say-fuck”则会被认为相同——服务器端逻辑接收到的都是“let-us-say-**”。也就是说,我们必须提供一个机制,让上面的HttpModule可以“忽略”掉某些内容。

如果是其他一些解决方案,我们可以在客户端进行一些特殊标记。例如在客户端增加一个“-noffw-password”字段来表示忽略对“password”字段的过滤。不过根据著名的“Don't trust the client”原则,这种做法应该是第一个被否决掉的。试想,如果某些哥们发现了这一点(别说“不可能”),那么想要绕开这种过滤方式实在是一件非常容易的事情。不过我们应该可以把这种“约定”直接运用在字段名上。例如原本我们如果取名为“password”的字段,现在直接使用“-noffw-password”,而HttpModule发现了这种前缀就会放它一马。由于字段的命名完全是由服务器端决定,因此采取这种方式之后客户端的恶人们就无法绕开我们的过滤了。

还有一种情况就是我们要对某些特定的字段采取一些特殊的过滤方式。例如,之前相当长的一段时间内我认为在服务器端反序列化一段JSON字符串是非常不合理的,不过由于AJAX几乎成了事实标准,亦或是现在的Web应用程序经常需要传递一些结构复杂的对象,JSON格式已经越来越多地被服务器端所接受。假如一个字段是表示一个JSON字符串,那么首先我们只应该对它的“值”进行过滤,而忽略其中的“键”。对于这种字段,我们依旧可以使用如上的命名约定来进行忽略。例如,我们可以使用-json-data的方法来告诉服务器端这个字段应该被当作JSON格式进行处理。

如何?其实问题远没有解决。

相关文章

  • 一个较完整的关键字过滤解决方案(上)
  • 一个较完整的关键字过滤解决方案(中)
  • 一个较完整的关键字过滤解决方案(下)

转载于:https://www.cnblogs.com/JeffreyZhao/archive/2008/12/22/filter-forbidden-word-solution.html

一个较完整的关键字过滤解决方案(上)相关推荐

  1. 转:一个较完整的关键字过滤解决方案

    一个较完整的关键字过滤解决方案(上) 一个较完整的关键字过滤解决方案(中) 一个较完整的关键字过滤解决方案(下)

  2. 分享一个完整的Mybatis分页解决方案

    分享一个完整的Mybatis分页解决方案 参考文章: (1)分享一个完整的Mybatis分页解决方案 (2)https://www.cnblogs.com/gev-1016/p/6606114.htm ...

  3. Scikit-Learn TensorFlow机器学习实用指南(二):一个完整的机器学习项目【上】

    机器学习实用指南(二):一个完整的机器学习项目[上] 作者:LeonG 本文参考自:<Hands-On Machine Learning with Scikit-Learn & Tens ...

  4. 事务(进程 ID 133)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品的解决方案

    事务(进程 ID 133)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品的解决方案 参考文章: (1)事务(进程 ID 133)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品的解决 ...

  5. 读写分离,读写分离死锁解决方案,事务发布死锁解决方案,发布订阅死锁解决方案|事务(进程 ID *)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务...

    前言:         由于网站访问压力的问题,综合分析各种因素后结合实际情况,采用数据库读写分离模式来解决当前问题.实际方案中采用"事务发布"模式实现主数据库和只读数据库的同步, ...

  6. 字符串多模式精确匹配(脏字/敏感词汇/关键字过滤算法)——TTMP算法 之实战F模式...

    前面那么多篇文章都太抽象,这次来一个稍微实际一点的.F模式是我实际上选用的模式,对该模式我做了不少实际的测试,因此代码也算是比较稳定的.不过由于实际上为了得到该算法的效率,算法本身做了一些优化,对于初 ...

  7. mfc 如何判断excel软件是否打开_如何从无到有地搭建一套完整的测试系统(上)...

    本文说明讨论了气体传感器的半导体制造商如何采用聚焦于测试的解决方案,该解决方案能够以较低的成本提供所需的准确度,容纳非常大的站点数量,并与高性能半导体测试系统的总体吞吐性能相匹配. 应用背景 MEMS ...

  8. Arco Design - 企业级产品的完整设计和开发解决方案

    目录 一.简介 二.创建项目 1.配置环境 2.安装 三.了解项目 1.技术栈 2.差异(对比我之前使用的技术栈) 四.总结 一.简介 官网:Arco Design - 企业级产品的完整设计和开发解决 ...

  9. 一个网站完整的SEO优化方案

    希望对SEO优化有点帮助,深咖直接跳过... 一个完整的SEO优化方案主要由四个小组组成: 前端/页编人员 内容编辑人员 推广人员 数据分析人员 接下来,我们就对这四个小组分配工作. No.1 首先, ...

最新文章

  1. 为什么static成员必须在类外初始化
  2. 入门|机器学习中常用的损失函数你知多少?
  3. 从Folly源码学C++ 11的新特性
  4. hibernate注解实体类(Emp.java)
  5. HDFS客户端的权限错误:Permission denied
  6. vue-quill-editor 获取无法获取光标位置
  7. 用汇编的眼光看C++(之算术符重载)
  8. React Native 首次加载白屏优化
  9. java提高篇(十三)-----字符串
  10. 计算机班英语试卷考法,计算机专业英语期末考试试卷A
  11. 维纳滤波python 函数_图像维纳滤波实现(1)
  12. 使用video speed controller给视频加速
  13. pwnablekr-asm-seccomp-sandbox
  14. Android Service(一) Service初识
  15. 数开头的成语有哪些_一至十数字开头的成语有哪些?
  16. 统计字符串中的大小写字母个数
  17. 椭圆机和跑步机哪个更好
  18. Echarts中国地图的china.js下载
  19. UIUC数学计算机专业,UIUC的Statistics「伊利诺伊大学香槟分校统计系」
  20. Unity物理系统中碰撞体、刚体、isKinematic、isTrigger的关系(附动画演示)

热门文章

  1. Python 安装库的方法及解决pip 安装时速度缓慢的方法
  2. 【dlib opencv - detector landmark】 ubuntu上针对dlib-hog和opencv haar人脸检测与landmar-68在不同平台上运行时间实验结果汇总
  3. 【Transformer】DPT: Vision Transformer for Dense Prediction
  4. 脉冲多普勒雷达_是人类还是动物? 多普勒脉冲雷达和神经网络的目标分类
  5. rw data 、ro data 和 code详解
  6. 贷款中介市场要变天了吗?
  7. 如何申请到利息低的贷款?
  8. Ubuntu中配置SSH服务
  9. 根据RTL图编写Verilog程序
  10. html表ge模板_精选甘特图模板,丰富又好用