mvc中AntiForgeryToken的实现方式--看Mvc源码

通过 AntiForgeryWorker的GetHtml()方法生成html --input hide元素--value=要验证的值,并生成cookie--用于保存需要验证的值。

类中的AntiForgeryDataSerializer--用作序列化与反序列化验证的值。

internal class AntiForgeryWorker {
        public AntiForgeryWorker() {
            Serializer = new AntiForgeryDataSerializer();
        }

        internal AntiForgeryDataSerializer Serializer {
            get;
            set;
        }

        private static HttpAntiForgeryException CreateValidationException() {
            return new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_ValidationFailed);
        }

        public HtmlString GetHtml(HttpContextBase httpContext, string salt, string domain, string path) {
            Debug.Assert(httpContext != null);

            string formValue = GetAntiForgeryTokenAndSetCookie(httpContext, salt, domain, path);
            string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);

            TagBuilder builder = new TagBuilder("input");
            builder.Attributes["type"] = "hidden";
            builder.Attributes["name"] = fieldName;
            builder.Attributes["value"] = formValue;
            return new HtmlString(builder.ToString(TagRenderMode.SelfClosing));
        }

        private string GetAntiForgeryTokenAndSetCookie(HttpContextBase httpContext, string salt, string domain, string path) {
            string cookieName = AntiForgeryData.GetAntiForgeryTokenName(httpContext.Request.ApplicationPath);

            AntiForgeryData cookieToken = null;
            HttpCookie cookie = httpContext.Request.Cookies[cookieName];
            if (cookie != null) {
                try {
                    cookieToken = Serializer.Deserialize(cookie.Value);
                }
                catch (HttpAntiForgeryException) { }
            }

            if (cookieToken == null) {
                cookieToken = AntiForgeryData.NewToken();
                string cookieValue = Serializer.Serialize(cookieToken);

                HttpCookie newCookie = new HttpCookie(cookieName, cookieValue) { HttpOnly = true, Domain = domain };
                if (!String.IsNullOrEmpty(path)) {
                    newCookie.Path = path;
                }
                httpContext.Response.Cookies.Set(newCookie);
            }

            AntiForgeryData formToken = new AntiForgeryData(cookieToken) {
                Salt = salt,
                Username = AntiForgeryData.GetUsername(httpContext.User)
            };
            return Serializer.Serialize(formToken);
        }

        public void Validate(HttpContextBase context, string salt) {
            Debug.Assert(context != null);

            string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
            string cookieName = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);

            HttpCookie cookie = context.Request.Cookies[cookieName];
            if (cookie == null || String.IsNullOrEmpty(cookie.Value)) {
                // error: cookie token is missing
                throw CreateValidationException();
            }
            AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);

            string formValue = context.Request.Form[fieldName];
            if (String.IsNullOrEmpty(formValue)) {
                // error: form token is missing
                throw CreateValidationException();
            }
            AntiForgeryData formToken = Serializer.Deserialize(formValue);

            if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal)) {
                // error: form token does not match cookie token
                throw CreateValidationException();
            }

            string currentUsername = AntiForgeryData.GetUsername(context.User);
            if (!String.Equals(formToken.Username, currentUsername, StringComparison.OrdinalIgnoreCase)) {
                // error: form token is not valid for this user
                // (don‘t care about cookie token)
                throw CreateValidationException();
            }

            if (!String.Equals(salt ?? String.Empty, formToken.Salt, StringComparison.Ordinal)) {
                // error: custom validation failed
                throw CreateValidationException();
            }
        }
    }
internal class AntiForgeryDataSerializer {
        [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")]
        public virtual AntiForgeryData Deserialize(string serializedToken) {
            if (String.IsNullOrEmpty(serializedToken)) {
                throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "serializedToken");
            }

            try {
                using (MemoryStream stream = new MemoryStream(Decoder(serializedToken)))
                using (BinaryReader reader = new BinaryReader(stream)) {
                    return new AntiForgeryData {
                        Salt = reader.ReadString(),
                        Value = reader.ReadString(),
                        CreationDate = new DateTime(reader.ReadInt64()),
                        Username = reader.ReadString()
                    };
                }
            }
            catch (Exception ex) {
                throw new HttpAntiForgeryException(WebPageResources.AntiForgeryToken_ValidationFailed, ex);
            }
        }

        [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "MemoryStream is resilient to double-Dispose")]
        public virtual string Serialize(AntiForgeryData token) {
            if (token == null) {
                throw new ArgumentNullException("token");
            }

            using (MemoryStream stream = new MemoryStream())
            using (BinaryWriter writer = new BinaryWriter(stream)) {
                writer.Write(token.Salt);
                writer.Write(token.Value);
                writer.Write(token.CreationDate.Ticks);
                writer.Write(token.Username);

                return Encoder(stream.ToArray());
            }
        }

        // Testing hooks

        internal Func<string, byte[]> Decoder =
            (value) => MachineKey.Decode(Base64ToHex(value), MachineKeyProtection.All);

        internal Func<byte[], string> Encoder =
            (bytes) => HexToBase64(MachineKey.Encode(bytes, MachineKeyProtection.All).ToUpperInvariant());

        // String transformation helpers

        private static string Base64ToHex(string base64) {
            StringBuilder builder = new StringBuilder(base64.Length * 4);
            foreach (byte b in Convert.FromBase64String(base64)) {
                builder.Append(HexDigit(b >> 4));
                builder.Append(HexDigit(b & 0x0F));
            }
            string result = builder.ToString();
            return result;
        }

        private static char HexDigit(int value) {
            return (char)(value > 9 ? value + ‘7‘ : value + ‘0‘);
        }

        private static int HexValue(char digit) {
            return digit > ‘9‘ ? digit - ‘7‘ : digit - ‘0‘;
        }

        private static string HexToBase64(string hex) {
            int size = hex.Length / 2;
            byte[] bytes = new byte[size];
            for (int idx = 0; idx < size; idx++) {
                bytes[idx] = (byte)((HexValue(hex[idx * 2]) << 4) + HexValue(hex[idx * 2 + 1]));
            }
            string result = Convert.ToBase64String(bytes);
            return result;
        }
    }

AntiForgeryData--验证保存的类,即值,使用RNGCryptoServiceProvider加密。

internal sealed class AntiForgeryData {

        private const string AntiForgeryTokenFieldName = "__RequestVerificationToken";

        private const int TokenLength = 128 / 8;
        private readonly static RNGCryptoServiceProvider _prng = new RNGCryptoServiceProvider();

        private DateTime _creationDate = DateTime.UtcNow;
        private string _salt;
        private string _username;
        private string _value;

        public AntiForgeryData() {
        }

        // copy constructor
        public AntiForgeryData(AntiForgeryData token) {
            if (token == null) {
                throw new ArgumentNullException("token");
            }

            CreationDate = token.CreationDate;
            Salt = token.Salt;
            Username = token.Username;
            Value = token.Value;
        }

        public DateTime CreationDate {
            get {
                return _creationDate;
            }
            set {
                _creationDate = value;
            }
        }

        public string Salt {
            get {
                return _salt ?? String.Empty;
            }
            set {
                _salt = value;
            }
        }

        public string Username {
            get {
                return _username ?? String.Empty;
            }
            set {
                _username = value;
            }
        }

        public string Value {
            get {
                return _value ?? String.Empty;
            }
            set {
                _value = value;
            }
        }

        private static string Base64EncodeForCookieName(string s) {
            byte[] rawBytes = Encoding.UTF8.GetBytes(s);
            string base64String = Convert.ToBase64String(rawBytes);

            // replace base64-specific characters with characters that are safe for a cookie name
            return base64String.Replace(‘+‘, ‘.‘).Replace(‘/‘, ‘-‘).Replace(‘=‘, ‘_‘);
        }

        private static string GenerateRandomTokenString() {
            byte[] tokenBytes = new byte[TokenLength];
            _prng.GetBytes(tokenBytes);

            string token = Convert.ToBase64String(tokenBytes);
            return token;
        }

        // If the app path is provided, we‘re generating a cookie name rather than a field name, and the cookie names should
        // be unique so that a development server cookie and an IIS cookie - both running on localhost - don‘t stomp on
        // each other.
        internal static string GetAntiForgeryTokenName(string appPath) {
            if (String.IsNullOrEmpty(appPath)) {
                return AntiForgeryTokenFieldName;
            }
            else {
                return AntiForgeryTokenFieldName + "_" + Base64EncodeForCookieName(appPath);
            }
        }

        internal static string GetUsername(IPrincipal user) {
            if (user != null) {
                IIdentity identity = user.Identity;
                if (identity != null && identity.IsAuthenticated) {
                    return identity.Name;
                }
            }

            return String.Empty;
        }

        public static AntiForgeryData NewToken() {
            string tokenString = GenerateRandomTokenString();
            return new AntiForgeryData() {
                Value = tokenString
            };
        }

    }

AntiForgery--用于对以上功能的公开类

public static class AntiForgery {
        private static readonly AntiForgeryWorker _worker = new AntiForgeryWorker();

        public static HtmlString GetHtml() {
            if (HttpContext.Current == null) {
                throw new ArgumentException(WebPageResources.HttpContextUnavailable);
            }

            return GetHtml(new HttpContextWrapper(HttpContext.Current), salt: null, domain: null, path: null);
        }

        public static HtmlString GetHtml(HttpContextBase httpContext, string salt, string domain, string path) {
            if (httpContext == null) {
                throw new ArgumentNullException("httpContext");
            }

            return _worker.GetHtml(httpContext, salt, domain, path);
        }

        public static void Validate() {
            if (HttpContext.Current == null) {
                throw new ArgumentException(WebPageResources.HttpContextUnavailable);
            }
            Validate(new HttpContextWrapper(HttpContext.Current), salt: null);
        }

        public static void Validate(HttpContextBase httpContext, string salt) {
            if (httpContext == null) {
                throw new ArgumentNullException("httpContext");
            }

            _worker.Validate(httpContext, salt);
        }
    }

使用ValidateAntiForgeryTokenAttribute特性,用于在要验证的方法上,其中默认是采用上面AntiForgery.Validate来验证。

也可以传自己的--salt生成验证的票据。

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public sealed class ValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter {

        private string _salt;

        public string Salt {
            get {
                return _salt ?? String.Empty;
            }
            set {
                _salt = value;
            }
        }

        internal Action<HttpContextBase, string> ValidateAction {
            get;
            private set;
        }

        public ValidateAntiForgeryTokenAttribute()
            : this(AntiForgery.Validate) {
        }

        internal ValidateAntiForgeryTokenAttribute(Action<HttpContextBase,string> validateAction) {
            Debug.Assert(validateAction != null);
            ValidateAction = validateAction;
        }

        public void OnAuthorization(AuthorizationContext filterContext) {
            if (filterContext == null) {
                throw new ArgumentNullException("filterContext");
            }

            ValidateAction(filterContext.HttpContext, Salt);
        }
    }

使用方式

1.在view里的form中生成验证值

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()

2.在对应的action中

       [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOn(LogOnModel model, string returnUrl)

从以上可分析可知,存值cookie和获取值(从form中获取)都是可以在负载中有效。

所以放心在负载中使用,增加你的程序的安全性。

时间: 2024-09-29 17:50:31

mvc中AntiForgeryToken的实现方式--看Mvc源码的相关文章

asp.net开发中常见公共捕获异常方式总结(附源码)

本文实例总结了asp.net开发中常见公共捕获异常方式.分享给大家供大家参考,具体如下: 前言:在实际开发过程中,对于一个应用系统来说,应该有自己的一套成熟的异常处理框架,这样当异常发生时,也能得到统一的处理风格,将异常信息优雅 地反馈给开发人员和用户.我们都知道,.net的异常处理是按照“异常链”的方式从底层向高层逐层抛出,如果不能尽可能地早判断异常发生的边界并捕获异 常,CLR会自动帮我们处理,但是这样系统的开销是非常大的,所以异常处理的一个重要原则是“早发现早抛出早处理”.但是本文总结的服

Spring 4 MVC HelloWorld 纯注解方式(带源码)【超赞】

[本系列其他教程正在陆续翻译中,点击分类:spring 4 mvc 进行查看] [翻译 by 明明如月 QQ 605283073] #项目下载地址:http://websystique.com/?smd_process_download=1&download_id=1722#. 上一篇:Spring 4 MVC hello world 教程-完全基于XML(带项目源码) 下一篇: Spring 4 MVC 表单校验资源处理(带源码) 在上一个例子:Spring 4 MVC hello world

Spring 4 MVC 表单校验资源处理(带源码)

[本系列其他教程正在陆续翻译中,点击分类:spring 4 mvc 进行查看] [翻译 by 明明如月 QQ 605283073] 上一篇:Spring 4 MVC HelloWorld 纯注解方式(带源码) 下一篇文章:Spring 4 MVC 视图解析器(XML JSON PDF等) 纯注解 #项目下载地址:http://websystique.com/?smd_process_download=1&download_id=1258# 本文我们将学习使用Spring 表单标签( Spring

边看MHA源码边学Perl语言之二 ManagerUtil

边看MHA源码边学Perl语言之二ManagerUtil.pm MHA版本 为了让大家有一个共同的代码学习环境,特别从网络找了mha4mysql-manager-0.56,mha4mysql-node-0.56稳定版作为学习和研究对象,大家可以到直接到github上进行clone: https://github.com/mysql-dev-fun/mha4mysql-manager-0.56 https://github.com/mysql-dev-fun/mha4mysql-node-0.56

带着问题看redux源码

前言 作为前端状态管理器,这个比较跨时代的工具库redux有很多实现和思想值得我们思考.在深入源码之前,我们可以相关注下一些常见问题,这样带着问题去看实现,也能更加清晰的了解. 常见问题 大概看了下主要有这么几个: redux三大原则 这个可以直接参考官方文档 redux 的优缺点. 关于优缺点,太主观了大家见仁见智. redux中间件相关,洋葱模型是什么,常见中间件. 背景 有关acton,reducer相关的部分可以看我前面的文章.我们主要关注针对store和中间件相关的部分来解读. sto

dyld方式遍历模块源码讲解

mac ios上遍历模块的有几种方式(其实不叫遍历模块,应该叫做遍历进程内所有的macho可执行文件,看完就知道为什么了).这里只看dyld方式遍历的,dyld大家都知道这个是水果支持动态链接启动macho文件用的,也就当你要依赖其它库时dyld会给也把这些坑填了,遍历模块代码是:https://blog.51cto.com/haidragon/2164203用到的函数有: int32_t nModNums= _dyld_image_count(); //获取所有image pModSlide

边看MHA源码边学Perl语言之三 NodeUtil.pm

边看MHA源码边学Perl语言之三 NodeUtil.pm NodeUtil.pm源码分析 MHA的代码分为mha4mysql-manager(管理节点)和mha4mysql-node(数据库节点)两部分,可能有些人认为mha4mysql-node只需要安装在数据库节点就可以了,但通过源码可看出,在管理点节也是需要安装node节点,因为在manager节点也会调用NodeUtil中的方法.以下为加过comment的mha4mysql-node的代码: #!/usr/bin/env perl #

c++实现游戏开发中常用的对象池(含源码)

c++实现游戏开发中常用的对象池(含源码) little_stupid_child2017-01-06上传 对象池的五要素: 1.对象集合 2.未使用对象索引集合 3.已使用对象索引集合 4.当前使用量 5.最大使用量 http://download.csdn.net/download/little_stupid_child/9730912

如何在Eclipse中查看JDK以及Java框架的源码

对于Java程序员来说,有时候是需要查看JDK或者一些Java框架的源码来分析问题的,而默认情况下,你按住Ctrl,再点击 Java本身的类库(例如ArrayList)是无法查看源码的,那么如何在Eclipse中查看JDK以及Java框架的源码呢?下面,跟着我 一起,一步步带你走进源码的世界. 方法一:快速简单 第一步: 打开你的Eclipse,然后随便找一个Java文件,随便找一个Java类库,比如String什么的,然后按住Ctrl,再点击它,你会发现跳到如下界面: 你会发现报错了:Sour