代码改变世界

自己动手实现AntiForgery。

2010-03-14 00:40  姜 萌@cnblogs  阅读(1988)  评论(2编辑  收藏  举报

为了防止恶意向服务器post数据(比如防止第三方程序模拟post行为)很多web的表单提交使用了特殊的手段,比如qq的登录,论坛的发帖,或是下载网站的放盗链,这些可以统称为AntiForgery。

在asp.net mvc中,我们可以通过Html.AntiForgery()来生成Token,并在相应的action上使用ValidateAntiForgeryTokenAttribute来让框架自动为我们实现AntiForgery(并可以配置提供两个参数Order和Salt(1.0版本))。

但有些时候我们还有其他需求,比如想qq登录那样如果页面数据传送给客户端后如果用户规定时间内不使用就按作废处理,或是不允许用户频繁的想web server post数据。

AntiForgery的实现原理

Tips:关于Ticks 与 Salt

Ticks,即时间戳,例如我们可以通过DateTime.UtcNow.Ticks得到的一个long值做Ticks。

Salt:这是一个服务器端才知道的数据,Salt与Ticks进行hash运算得到一个Token,由于服务端不向客户公开的Salt值,所以用户即使修改cookie中的Ticks但无法得到匹配的Token值(当然懂密码学方面的弟兄可以推导出Salt值和使用的算法进而推导出Token。)。

服务端在处理响应时会做两件事:一是并在response中设置Set-Cookie值为对应值,二是将Ticks与salt联合得到一个Token值写到页面中去。

在客户端再一次向服务端发出http请求时,如果服务器认为需要验证token,就会从post过来的数据中找到ticks(一般由cookie记录)和token(可以使用隐藏域)值(如果没有提供当然就是违规的请求了),并再一次检查ticks与salt的计算结果是否与token相等,如果不相等就是违规的请求。

具体实现

对于.NET,System.Security.Cryptography命名空间下有很多加密/解密算法类,我们可以任选一个对Ticks和Salt进行计算。

用于计算Token的AntiForgeryToken类:

namespace SopacoNetAnc.Mvc.Infrastructure
{
    public class AntiForgeryToken
    {
        #region Constructors & Initializer
        public AntiForgeryToken(string salt)
        {
            _salt = salt;
            Ticks = generateTicks();
        }
        #endregion

        #region Fields
        private string _salt;
        public static readonly string TicksName = "sopacoAntiForgeryTicks";
        public static readonly string TokenName = "sopacoAntiForgeryToken";
        #endregion

        #region Properties
        public long Ticks
        {
            get;
            set;
        }
        public string Value
        {
            get
            {
                return GetHash(_salt, Ticks);
            }
        }
        #endregion
        #region public static Helper Methods
        public static string GetHash(string salt, long ticks)
        {
            SHA512 hasher = new SHA512Managed();
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(string.Concat(salt, ticks));
            hasher.ComputeHash(bytes);
            return Convert.ToBase64String(bytes);
        }

        #endregion

        #region private Helper Methods
        private long generateTicks()
        {
            return DateTime.UtcNow.Ticks;
        }
        #endregion
    }
}

然后我们可以定义一个类去实现IAuthorizationFilter(对于ASP.NET MVC,如果是java的放到Filter或前端控制器中),然后在OnAuthorization中去校验时间戳是否过期以及Ticks与Token是否相符。

AntiForgeryToken antiForgeryToken = new AntiForgeryToken(salt); //salt自己去规定可以事先弄个Guid

            long ticks;

            string formTicks = filterContext.HttpContext.Request.Cookies.Get(AntiForgeryToken.TicksName) != null
                ? filterContext.HttpContext.Request.Cookies.Get(AntiForgeryToken.TicksName).Value
                : null;

            string formHash = filterContext.HttpContext.Request.Form[AntiForgeryToken.TokenName];

            if (string.IsNullOrEmpty(formTicks) || !long.TryParse(formTicks, out ticks) || string.IsNullOrEmpty(formHash))
            {
                throw new HttpAntiForgeryException("Bad Anti-Forgery Token");
            }

            TimeSpan timeOffset = new TimeSpan(antiForgeryToken.Ticks - ticks);
            if (!(AntiForgeryToken.GetHash(salt, ticks.ToString()) == formHash && timeOffset.TotalMinutes < 120))
            {
                throw new HttpAntiForgeryException("Bad Anti-Forgery Token");
            }