服务器端处理重复的POST请求

时间:2016-09-05 16:08:42

标签: c# asp.net-mvc post asp.net-mvc-5 duplicates

TL; DR:我想实现此行为:

  1. 我使用自定义在控制器中装饰特定的POST操作 attribute,仅指定自定义int参数,以秒为单位, 像[PreventSpam(Seconds = 10)] =>来自同一客户的两个请求 如果POST对象相同且被认为是“重复” 两者之间的时间跨度小于“秒”
  2. 处理请求时,如果检测到“重复” (根据以前的定义)它不应该被处理。 相反,ActionResult(无论是什么,一个视图,一个JSON ..)的 应返回之前的非重复请求。当然那样 意味着ActionResult应该被第一个缓存 请求。
  3. 如果我能够实现这一点,我再也不用担心用户在提交请求时执行多次点击,从而复制请求,而不必通过javascript手动处理。

    我遇到了MVC 5的以下问题:当用户快速单击表单的提交按钮时,会生成多个相同的POST请求。

    为了安全起见,我们希望处理此问题服务器端(如果我只通过javascript禁用提交按钮,没有人可以阻止恶意用户提交多个请求。) < / p>

    我想实现以下结果:当在特定动作上检测到多个相同的请求时(假设它们之间的时间间隔小于10秒),仅考虑第一个请求 - &gt; 以下所有要求应完全针对第一次请求所产生的“ActionResult”,这些请求应该被删除,因此该行动仅在执行后执行

    从本指南(http://rion.io/2013/02/24/prevent-repeated-requests-using-actionfilters-in-asp-net-mvc/)中获取灵感,它实现了类似的功能(如果检测到多个请求,则会添加模型状态错误..),我已经提出了这个代码:

    public class PreventSpamAttribute : ActionFilterAttribute
    {
        //This stores the time between Requests (in seconds)
        public int DelayRequest = 10;
    
        //THIS IS CALLED *AFTER* THE FIRST ACTION HAS BEEN PROPERLY EXECUTED -> THE ACTIONRESULT OBJECT HAS BEEN DETERMINED
        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            var cache = filterContext.HttpContext.Cache;
    
            //I CACHE THE ACTION RESULT FOR "DelayRequest" SECONDS, USING THE GENERATED HASH AS KEY
            cache.Add(_getHash(filterContext.HttpContext), filterContext.Result, null, DateTime.Now.AddSeconds(DelayRequest), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
    
            base.OnResultExecuted(filterContext);
        }
    
        //THIS IS CALLED *BEFORE* CALLING THE ACTION
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var cache = filterContext.HttpContext.Cache;
    
            var hash = _getHash(filterContext.HttpContext);
    
            //IF I ALREADY HAVE A CACHED RESULT IT MEANS THAT THE USER CLICKED MULTIPLE TIMES,
            //INSTEAD OF CALLING THE ACTION I SIMPLY RETURN THE ACTIONRESULT THAT I CACHED..
            if (cache[hash] != null)            
                filterContext.Result = (ActionResult)cache[hash];
    
            base.OnActionExecuting(filterContext);
        }
    
        //GENERATES UNIQUE HASH, CONSIDERING VARIOUS REQUEST PARAMETERS
        private string _getHash(HttpContextBase httpContext)
        {
            //Store our HttpContext (for easier reference and code brevity)
            var request = httpContext.Request;
            //Store our HttpContext.Cache (for easier reference and code brevity)
            var cache = httpContext.Cache;
    
            //Grab the IP Address from the originating Request (very simple implementation for example purposes)
            var originationInfo = request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? request.UserHostAddress;
    
            //Append the User Agent
            originationInfo += request.UserAgent;
    
            //Now we just need the target URL Information
            var targetInfo = request.RawUrl + request.QueryString;
    
            //Generate a hash for your strings (this appends each of the bytes of the value into a single hashed string
            return string.Join("", MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(originationInfo + targetInfo)).Select(s => s.ToString("x2")));
        }
    }
    

    不幸的是,我觉得这种方法存在一个主要问题:如果此操作需要一些时间来生成ActionResult会怎么样?当重复请求到达服务器时,另一个线程可能仍在处理第一个,所以ActionResult还没有被缓存。所以请求再次被处理。即使动作本身是同步的,如果我理解如果对同一动作有多个同时请求,则来自线程池的不同线程将被分配不同的请求,以便它们可以同时执行。这是对的吗?

    我的最终目标: 处理服务器端的所有内容,即使用户多次按下表单中的提交按钮,也只会发生一次按下

    更新07/09/2016: 在这种情况下,将令牌存储在每个表单的隐藏字段中并不实际:在我们的一些视图中,我们通过javascript手动调用post操作:

    myUrl = '@Url.Action("MyAction","MyController")';
    
    myPostObject = {
       entityId: 15,
       someValue: "aValue",
    }
    
    $.post(myUrl, myPostObject, callbackFunction);
    

    必须手动编辑每个“myPostObject”以包含额外的GUID字段,这将非常耗时且容易出错。这就是我们寻找完全服务器端的解决方案的原因,并且只需要使用自定义属性来修饰相关操作。

1 个答案:

答案 0 :(得分:0)

我还建议这里的概念存在一个重大缺陷 - 如果您的网站位于网络负载均衡器后面,则任何MVC代码都无法告知它已经在另一台服务器或IIS上收到了相同的请求实例。

如果IIS处于负载状态,IIS将很乐意启动另一个线程(在WebGarden模式下),因此您的单个应用程序实例永远无法可靠地判断用户是否正在使用重复的帖子攻击。

最终,您的代码将调用某处的着色状态服务器(通常是数据库,但可能是共享内存缓存),这时您可以判断用户是否已提交请求。