艾伟:一个较完整的关键字过滤解决方案(上)

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

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

有趣的需求

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

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

实现似乎很简单

  数据结构中的单向链表可谓无比经典。有人说:单向链表的题目好难啊,没法逆序查找,很多东西都不容易做。有人却说:单向链表既然只能向一个方向遍历,那么变化就会很有限,所以题目不会过于复杂。老赵觉得后者的说法不无道理。例如在现在的问题上,我们如果要在一个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格式进行处理。

  如何?

  其实问题远没有解决——尽情期待《一个较完整的关键字过滤解决方案(下)》。

 

目录
相关文章
|
6月前
|
机器学习/深度学习 算法 安全
内容过滤算法:构建数字世界的守护者
内容过滤算法:构建数字世界的守护者
|
3月前
|
JSON API 开发者
苏宁关键字搜索接口详解
苏宁关键字搜索接口服务让开发者通过API输入关键字获取平台商品信息。支持价格区间、品牌等多种筛选,精确查找。需先注册开发者账号,创建应用获API密钥。调用时指定搜索词、分类、价格等参数,并使用个人API密钥。Python示例展示了请求及解析JSON响应的方法,包括商品名、价格等详情。注意替换密钥和个人调用频率限制。此接口对企业搜索、选品等业务至关重要。
|
4月前
|
XML JSON API
阿里巴巴关键字搜索接口技术详解
阿里巴巴开放平台提供关键字搜索API,让开发者能高效检索商品信息。接口涉及注册获取API密钥、构建HTTP请求、发送请求并解析JSON或XML响应。功能包括商品查询、排序、筛选、分页及结果格式化。使用流程包括注册、理解文档、构建请求、处理响应和错误管理。注意事项包括遵守规则、关键字优化、高效使用筛选与分页,以及确保数据处理的准确性和跟踪官方更新。此API助力商家和开发者提升搜索效率和业务性能。
|
5月前
|
SQL 数据库 UED
条件筛选大作战:解析Where与Having的区别与应用
条件筛选大作战:解析Where与Having的区别与应用
41 0
运筹规划时复杂条件转换(最大M方式)
运筹规划时复杂条件转换(最大M方式)
64 0
|
数据挖掘
基于R筛选过滤低丰度物种的几种方式
基于R筛选过滤低丰度物种的几种方式
438 0
|
JavaScript 开发者 索引
品牌案例-根据关键字实现数组的过滤|学习笔记
快速学习品牌案例-根据关键字实现数组的过滤
146 0
《Drools 7 规则引擎教程》番外篇-规则条件匹配机制
《Drools 7 规则引擎教程》番外篇-规则条件匹配机制
335 0
|
搜索推荐 Serverless 定位技术
电商/O2O行业搜索排序表达式最佳实践
搭建搜索功能不难,难的是如何提高搜索质量,帮助用户快速找到心中所想的内容或商品,那么搜索结果的相关性排序则是影响用户体验最关键的一环,今天小编和大家聊一聊[开放搜索]几个典型的排序表达式的应用,如何更好的优化电商/O2O行业的排序效果.
3023 0
电商/O2O行业搜索排序表达式最佳实践
|
算法
艾伟:一个简单的关键字过滤算法
早上看到老赵的《一个较完整的关键字过滤解决方案(上)》文章,讲到怎样在项目中嵌入过滤方案的问题,以及提到 xingd 和 sumtec 两位大师发表的系列互拼的文章,在此我也忍不住谈谈自己遇到的问题以及一个的简化版的算法。
1005 3