zoukankan      html  css  js  c++  java
  • MVC_防止HttpPost重复提交

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

    public interface IPageTokenView
    {
        /// <summary>
        /// Generates the page token.
        /// </summary>
        string GeneratePageToken();
    
        /// <summary>
        /// Gets the get last page token from Form
        /// </summary>
        string GetLastPageToken { get; }
    
        /// <summary>
        /// Gets a value indicating whether [tokens match].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [tokens match]; otherwise, <c>false</c>.
        /// </value>
        bool TokensMatch { get; }
    }


    定义一个Abstract Class,包含一个

    public abstract class PageTokenViewBase : IPageTokenView
    {
        public static readonly string HiddenTokenName = "hiddenToken";
        public static readonly string SessionMyToken = "Token";
    
        /// <summary>
        /// Generates the page token.
        /// </summary>
        /// <returns></returns>
        public abstract string GeneratePageToken();
    
        /// <summary>
        /// Gets the get last page token from Form
        /// </summary>
        public abstract string GetLastPageToken { get; }
    
        /// <summary>
        /// Gets a value indicating whether [tokens match].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [tokens match]; otherwise, <c>false</c>.
        /// </value>
        public abstract bool TokensMatch { get; }
      
    }


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

        public class SessionPageTokenView : PageTokenViewBase
        {
            #region PageTokenViewBase
    
            /// <summary>
            /// Generates the page token.
            /// </summary>
            /// <returns></returns>
            public override string GeneratePageToken()
            {
                if (HttpContext.Current.Session[SessionMyToken] != null)
                {
                    return HttpContext.Current.Session[SessionMyToken].ToString();
                }
                else
                {
                    var token = GenerateHashToken();
                    HttpContext.Current.Session[SessionMyToken] = token;
                    return token;
                }
            }
    
            /// <summary>
            /// Gets the get last page token from Form
            /// </summary>
            public override string GetLastPageToken
            {
                get
                {
                    return HttpContext.Current.Request.Params[HiddenTokenName];
                }
            }
    
            /// <summary>
            /// Gets a value indicating whether [tokens match].
            /// </summary>
            /// <value>
            ///   <c>true</c> if [tokens match]; otherwise, <c>false</c>.
            /// </value>
            public override bool TokensMatch
            {
                get
                {
                    string formToken = GetLastPageToken;
                    if (formToken != null)
                    {
                        if (formToken.Equals(GeneratePageToken()))
                        {
                            //Refresh token
                            HttpContext.Current.Session[SessionMyToken] = GenerateHashToken();
                            return true;
                        }
                    }
                    return false;
                }
            }
    
            #endregion 
    
            #region Private Help Method
            /// <summary>
            /// Generates the hash token.
            /// </summary>
            /// <returns></returns>
            private string GenerateHashToken()
            {
                return Utility.Encrypt(
                    HttpContext.Current.Session.SessionID + DateTime.Now.Ticks.ToString());
            } 
            #endregion
    



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

    public static string Encrypt(string plaintext)
    {
        string cl1 = plaintext;
        string pwd = string.Empty;
        MD5 md5 = MD5.Create();
        byte[] s = md5.ComputeHash(Encoding.Unicode.GetBytes(cl1));
        for (int i = 0; i < s.Length; i++)
        {
            pwd = pwd + s[i].ToString("X");
        }
        return pwd;
    }


    我们再来编写一个Attribute继承FilterAttribute, 实现IAuthorizationFilter接口。然后比较Form中Token与Session中是否一致,不一致就Throw Exception. Tips:这里最好使用依赖注入IPageTokenView类型,增加Logging 等机制 

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class ValidateReHttpPostTokenAttribute : FilterAttribute, IAuthorizationFilter
    {
        public IPageTokenView PageTokenView { get; set; }
    
        /// <summary>
        /// Initializes a new instance of the <see cref="ValidateReHttpPostTokenAttribute"/> class.
        /// </summary>
        public ValidateReHttpPostTokenAttribute()
        {
            //It would be better use DI inject it.
            PageTokenView = new SessionPageTokenView();
        }
    
        /// <summary>
        /// Called when authorization is required.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }
    
            if (!PageTokenView.TokensMatch)
            {
                //log...
                throw new Exception("Invaild Http Post!");
            }
          
        }
    }


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

    public static HtmlString GenerateVerficationToken(this HtmlHelper htmlhelper)
    {
        string formValue = Utility.Encrypt(HttpContext.Current.Session.SessionID+DateTime.Now.Ticks.ToString());
        HttpContext.Current.Session[PageTokenViewBase.SessionMyToken] = formValue;
    
        string fieldName = PageTokenViewBase.HiddenTokenName;
        TagBuilder builder = new TagBuilder("input");
        builder.Attributes["type"] = "hidden";
        builder.Attributes["name"] = fieldName;
        builder.Attributes["value"] = formValue;
        return new HtmlString(builder.ToString(TagRenderMode.SelfClosing));
    }


    将输出这类的HtmlString: 

    <input name="hiddenToken" type="hidden" value="1AB01826F590A1829E65CBD23CCE8D53" />


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

    @using Mvc3App.Models;
    @Html.GenerateVerficationToken()
    

    假设我们这里有一个简单的Login.cshtml,然后插入其中:
       <form method="post" id="form1" action="@Url.Action("Index")">
        <p>
            @Html.Partial("_ViewToken")
            UserName:<input type="text" id="fusername" name="fusername" /><br />
            Password:<input type="password" id="fpassword" name="fpassword" />
            <input type="submit" value="Sign-in" />
        </p>
        </form>
    

    这里我们Post的Index Action,看Controller代码,我们在Index上加上ValidateReHttpPostToken的attribute.


    [HttpPost]
    [ValidateReHttpPostToken]
    public ActionResult Index(FormCollection formCollection)
    {
        return View();
    }
    
    public ActionResult Login()
    {
        return View();
    }
  • 相关阅读:
    Vue--爬坑
    小程序--爬坑
    同源策略
    如何更改placeholder属性中文字颜色
    vue 项目上传到码云,push时error: failed to push some refs to 'https://gitee.com/mawenrou/vue_ht.git'
    node服务端口被占用
    webpack配置自动打包重新运行npm run dev出现报错
    解决回调地狱
    Apache Spark
    RAM computer
  • 原文地址:https://www.cnblogs.com/ingstyle/p/6762132.html
Copyright © 2011-2022 走看看