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中获取)都是可以在负载中有效。

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

posted @ 2015-01-24 19:17  英雄饶命啊  阅读(528)  评论(0编辑  收藏  举报