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中获取)都是可以在负载中有效。
所以放心在负载中使用,增加你的程序的安全性。
用对方法才有效率,做对事情才有效果
“麻烦”是自己“处理”不当的结果
“困难”是自己“学习”不够的反射
“挫折”是自己“努力”不足的代价
“麻烦”是自己“处理”不当的结果
“困难”是自己“学习”不够的反射
“挫折”是自己“努力”不足的代价