Asp.net MVC中防止HttpPost重复提交

重复提交的场景很常见,可能是当时服务器延迟的原因,如购物车物品叠加,重复提交多个订单。常见的解决方法是提交后把Button在客户端Js禁用,或是用Js禁止后退键等。在ASP.NET MVC 3 Web Application中 如何去防止这类HTTP-Post的重复提交呢? 我们可以借助Session,放置一个Token在View/Page上,然后在Server端去验证是不是同一个Token来判断此次Http-Post是否有效。看下面的代码:  首先定义一个接口,便于扩展。

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public interface IPageTokenView  
  2. {  
  3.     /// <summary>  
  4.     /// Generates the page token.  
  5.     /// </summary>  
  6.     string GeneratePageToken();  
  7.   
  8.     /// <summary>  
  9.     /// Gets the get last page token from Form  
  10.     /// </summary>  
  11.     string GetLastPageToken { get; }  
  12.   
  13.     /// <summary>  
  14.     /// Gets a value indicating whether [tokens match].  
  15.     /// </summary>  
  16.     /// <value>  
  17.     ///   <c>true</c> if [tokens match]; otherwise, <c>false</c>.  
  18.     /// </value>  
  19.     bool TokensMatch { get; }  
  20. }  

定义一个Abstract Class,包含一个

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public abstract class PageTokenViewBase : IPageTokenView  
  2. {  
  3.     public static readonly string HiddenTokenName = "hiddenToken";  
  4.     public static readonly string SessionMyToken = "Token";  
  5.   
  6.     /// <summary>  
  7.     /// Generates the page token.  
  8.     /// </summary>  
  9.     /// <returns></returns>  
  10.     public abstract string GeneratePageToken();  
  11.   
  12.     /// <summary>  
  13.     /// Gets the get last page token from Form  
  14.     /// </summary>  
  15.     public abstract string GetLastPageToken { get; }  
  16.   
  17.     /// <summary>  
  18.     /// Gets a value indicating whether [tokens match].  
  19.     /// </summary>  
  20.     /// <value>  
  21.     ///   <c>true</c> if [tokens match]; otherwise, <c>false</c>.  
  22.     /// </value>  
  23.     public abstract bool TokensMatch { get; }  
  24.     
  25. }  

接着是实现SessionPageTokenView类型,记得需要在验证通过后生成新的Token,对于这个Class是把它放到Session中。

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class SessionPageTokenView : PageTokenViewBase  
  2.    {  
  3.        #region PageTokenViewBase  
  4.   
  5.        /// <summary>  
  6.        /// Generates the page token.  
  7.        /// </summary>  
  8.        /// <returns></returns>  
  9.        public override string GeneratePageToken()  
  10.        {  
  11.            if (HttpContext.Current.Session[SessionMyToken] != null)  
  12.            {  
  13.                return HttpContext.Current.Session[SessionMyToken].ToString();  
  14.            }  
  15.            else  
  16.            {  
  17.                var token = GenerateHashToken();  
  18.                HttpContext.Current.Session[SessionMyToken] = token;  
  19.                return token;  
  20.            }  
  21.        }  
  22.   
  23.        /// <summary>  
  24.        /// Gets the get last page token from Form  
  25.        /// </summary>  
  26.        public override string GetLastPageToken  
  27.        {  
  28.            get  
  29.            {  
  30.                return HttpContext.Current.Request.Params[HiddenTokenName];  
  31.            }  
  32.        }  
  33.   
  34.        /// <summary>  
  35.        /// Gets a value indicating whether [tokens match].  
  36.        /// </summary>  
  37.        /// <value>  
  38.        ///   <c>true</c> if [tokens match]; otherwise, <c>false</c>.  
  39.        /// </value>  
  40.        public override bool TokensMatch  
  41.        {  
  42.            get  
  43.            {  
  44.                string formToken = GetLastPageToken;  
  45.                if (formToken != null)  
  46.                {  
  47.                    if (formToken.Equals(GeneratePageToken()))  
  48.                    {  
  49.                        //Refresh token  
  50.                        HttpContext.Current.Session[SessionMyToken] = GenerateHashToken();  
  51.                        return true;  
  52.                    }  
  53.                }  
  54.                return false;  
  55.            }  
  56.        }  
  57.  
  58.        #endregion   
  59.  
  60.        #region Private Help Method  
  61.        /// <summary>  
  62.        /// Generates the hash token.  
  63.        /// </summary>  
  64.        /// <returns></returns>  
  65.        private string GenerateHashToken()  
  66.        {  
  67.            return Utility.Encrypt(  
  68.                HttpContext.Current.Session.SessionID + DateTime.Now.Ticks.ToString());  
  69.        }   
  70.        #endregion  

这里有到一个简单的加密方法,你可以实现自己的加密方法. 

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public static string Encrypt(string plaintext)  
  2. {  
  3.     string cl1 = plaintext;  
  4.     string pwd = string.Empty;  
  5.     MD5 md5 = MD5.Create();  
  6.     byte[] s = md5.ComputeHash(Encoding.Unicode.GetBytes(cl1));  
  7.     for (int i = 0; i < s.Length; i++)  
  8.     {  
  9.         pwd = pwd + s[i].ToString("X");  
  10.     }  
  11.     return pwd;  
  12. }  

我们再来编写一个Attribute继承 FilterAttribute , 实现 IAuthorizationFilter 接口。然后比较Form中Token与Session中是否一致,不一致就Throw Exception. Tips:这里最好使用依赖注入IPageTokenView类型,增加Logging 等机制 
[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]  
  2. public sealed class ValidateReHttpPostTokenAttribute : FilterAttribute, IAuthorizationFilter  
  3. {  
  4.     public IPageTokenView PageTokenView { getset; }  
  5.   
  6.     /// <summary>  
  7.     /// Initializes a new instance of the <see cref="ValidateReHttpPostTokenAttribute"/> class.  
  8.     /// </summary>  
  9.     public ValidateReHttpPostTokenAttribute()  
  10.     {  
  11.         //It would be better use DI inject it.  
  12.         PageTokenView = new SessionPageTokenView();  
  13.     }  
  14.   
  15.     /// <summary>  
  16.     /// Called when authorization is required.  
  17.     /// </summary>  
  18.     /// <param name="filterContext">The filter context.</param>  
  19.     public void OnAuthorization(AuthorizationContext filterContext)  
  20.     {  
  21.         if (filterContext == null)  
  22.         {  
  23.             throw new ArgumentNullException("filterContext");  
  24.         }  
  25.   
  26.         if (!PageTokenView.TokensMatch)  
  27.         {  
  28.             //log...  
  29.             throw new Exception("Invaild Http Post!");  
  30.         }  
  31.         
  32.     }  
  33. }  

还需要一个HtmlHelper的扩展方法:

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public static HtmlString GenerateVerficationToken(this HtmlHelper htmlhelper)  
  2. {  
  3.     string formValue = Utility.Encrypt(HttpContext.Current.Session.SessionID+DateTime.Now.Ticks.ToString());  
  4.     HttpContext.Current.Session[PageTokenViewBase.SessionMyToken] = formValue;  
  5.   
  6.     string fieldName = PageTokenViewBase.HiddenTokenName;  
  7.     TagBuilder builder = new TagBuilder("input");  
  8.     builder.Attributes["type"] = "hidden";  
  9.     builder.Attributes["name"] = fieldName;  
  10.     builder.Attributes["value"] = formValue;  
  11.     return new HtmlString(builder.ToString(TagRenderMode.SelfClosing));  
  12. }  

将输出这类的HtmlString: 

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <input name="hiddenToken" type="hidden" value="1AB01826F590A1829E65CBD23CCE8D53" />  

我们创建一个叫_ViewToken.cshtml的Partial View,这样便于模块化,让我们轻易加入到具体View里,就两行代码,第一行是扩展方法NameSpace

[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @using Mvc3App.Models;  
  2. @Html.GenerateVerficationToken()  

假设我们这里有一个简单的Login.cshtml,然后插入其中:
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <form method="post" id="form1" action="@Url.Action("Index")">  
  2.     <p>  
  3.         @Html.Partial("_ViewToken")  
  4.         UserName:<input type="text" id="fusername" name="fusername" /><br />  
  5.         Password:<input type="password" id="fpassword" name="fpassword" />  
  6.         <input type="submit" value="Sign-in" />  
  7.     </p>  
  8.     </form>  

这里我们Post的Index  Action,看Controller代码,我们在Index上加上ValidateReHttpPostToken的attribute.
[csharp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. [HttpPost]  
  2. [ValidateReHttpPostToken]  
  3. public ActionResult Index(FormCollection formCollection)  
  4. {  
  5.     return View();  
  6. }  
  7.   
  8. public ActionResult Login()  
  9. {  
  10.     return View();  
  11. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值