2016-09-05 241 views
0

TL; DR:我想實現此行爲:服務器端處理請求

  1. 我裝飾用自定義控制器的具體POST操作 屬性,它僅指定一個自定義int參數,以秒爲單位, like [PreventSpam(Seconds = 10)] =>如果POST對象相同且兩者之間的時間間隔小於「秒」,則來自同一客戶端 的兩個請求被視爲「重複」
  2. 處理請求時,如果它被檢測爲「重複」 (根據以前的定義),它不應該被處理。 應該返回先前的非重複請求的ActionResult(不管它是什麼,視圖,JSON ..)。當然, 意味着ActionResult應該已經被第一個 請求緩存了。

如果我能實現這一點,我永遠不會有關於何時提交一個請求,因此複製的要求,而不必通過javascript來手工處理這一執行多次點擊的用戶再次擔心。

我遇到了MVC 5的下列問題:當用戶快速點擊表單的提交按鈕時,會生成多個相同的POST請求。

爲了安全起見,我們想解決這個問題的服務器端

我(沒有人可以阻止惡意用戶提交多個請求,如果我只能通過JavaScript ..禁用提交按鈕)想要達到以下結果:當在特定動作(假設它們之間的時間間隔小於10秒)上檢測到多個相同的請求時,僅考慮第一請求 - >所有以下請求都應該正確指向由第一個請求生成的「ActionResult」同樣需要被緩存,因此該行爲僅在實際執行之前一次性執行。

與本指南(http://rion.io/2013/02/24/prevent-repeated-requests-using-actionfilters-in-asp-net-mvc/),達到類似的東西(如果檢測到多個請求添加了的ModelState錯誤..)取得靈感,我想出了這個代碼:

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尚未被高速緩存。因此,請求再次被處理。即使動作本身是同步的,至於我的理解是否有多個對同一動作的同時請求,來自線程池的不同線程將被分配不同的請求,以便它們可以被同時執行。它是否正確?

我的最終目標: 處理各種事情,服務器端,即使用戶按下的形式多次提交按鈕,沒有什麼比按壓一個單一的時間不同應該發生

UPDATE 07/09/2016: 在每個表單中的隱藏字段存儲代是不是在這種情況下真正實用:在幾個我們的意見,我們手動調用通過JavaScript後的動作,以這種方式:

myUrl = '@Url.Action("MyAction","MyController")'; 

myPostObject = { 
    entityId: 15, 
    someValue: "aValue", 
} 

$.post(myUrl, myPostObject, callbackFunction); 

必須手動編輯每個「myPostObject」以包含額外的GUID字段將非常耗時並且容易出錯。這就是爲什麼我們正在尋找完全服務器端的解決方案,並且只需要用自定義屬性修飾相關操作。

+0

請注意,模型 - 視圖 - 控制器標籤是關於模式的問題。 ASP.NET-MVC實現有一個特定的標籤。 –

+0

你當然是對的。感謝您指出了這一點。 –

回答

0

我也建議這裏的概念存在一個主要缺陷 - 如果你的網站在網絡負載平衡器後面,任何MVC代碼都不能說明它已經在另一個服務器上接收到相同的請求,或者IIS實例。

如果IIS的負載很低,IIS將很高興地啓動另一個線程(在WebGarden模式下),因此您的單個應用程序實例永遠無法可靠地判斷用戶是否使用重複後期攻擊。

最終,您的代碼會在某處(數據庫,通常可能是共享內存緩存)調出陰影狀態服務器,也就是說您可以判斷用戶是否已提交請求。

+0

好的,但是如果我獲得對單個會話上下文中存在的對象的鎖定:獲取OnActionExecuting(..)上的鎖定並釋放鎖定OnResultExecuted(..)。這不會強制每個重複的請求等待,直到第一個請求正確緩存ActionResult,同時仍然能夠同時處理不同的請求(屬於不同的會話)(因爲在這種情況下鎖定的對象會不同。 。)? –

+0

這可能會強制IIS啓動另一個實例,因爲它認爲之前的調用仍在處理中。 如果你想有一個非常輕便,而不是不惜一切防止萬無一失的方法,多點擊轉播只需添加一個隱藏的GUID來的頁面,當你POST檢查,看它是否存在於高速緩存 - 如果它不不,然後添加它。如果是這樣,你知道它的第二篇文章。 – PhillipH

+0

我不確定我瞭解你的意思。我的想法是:考慮同一客戶端,當收到第一個請求時,會話對象的鎖定被獲取並且執行操作。如果在完全處理第一個請求之前從同一客戶端收到第二個請求,則該第二個請求將在鎖定語句之前阻塞。當第一個請求被完全處理(並且actionResult存儲在緩存中)時,鎖定被釋放。第二個請求現在可以獲取該鎖。它檢測到有一個緩存的ActionResult,它立即返回。我錯在哪裏? –