本文转自这篇文章

XSRF即在访问B站点的时候,执行了A站点的功能。 
比如: 
A站点登录后,可以修改用户的邮箱(接口:/Email/Modify?email=123),修改邮箱时只验证用户有没有登录,而且登录信息是保存在cookie中。 
用户登录A站点后,又打开一个窗口访问B站点,如果这时B站点内嵌入了一条链接http://www.A.com/Email/Modify?email=123,当用户点击这条链接时会直接修改A站点的用户邮箱。

对表达提交来说,要关注的就是安全问题。ASP.NET MVC 提供了探测某种攻击类型的机制,其中一个措施就是防伪造令牌。这种令牌包含服务器端和客户端组件,代码会在表单中插入一个隐藏域以保护用户特定的令牌:

@Html.AntiForgeryToken()

在执行@Html.AntiForgeryToken()语句时,会在cookie中写入一个经过加密后的数据,并在页面中添加一个隐藏域并写入加密后的数据(默认名称为__RequestVerificationToken)。当执行IndexPost(前面示例)方法前,会判断cookie中的数据与隐藏域的数据是否相等。相等则验证通过。否则会抛出异常。(Post请求会自动把隐藏域传递到后台,如果是Get请求,就需要手动把隐藏域的值传递到后台)。 
待加密的数据是一个AntiForgeryToken对象。系统进行验证时,会先把加密的数据还原成AntiForgeryToken对象,对象有一个SecurityToken属性(用于填充随机序列),系统主要判断该字段的值是否相等。 
同一个会话期间,SecurityToken数据相同,所以即使开多个tab访问相同页面,数据验证也会通过。 
同一个会话期间cookie中的加密数据不会改变,因为访问页面时,cookie会传到后台,后台判断cookie中有加密数据,就不会重新生成cookie数据。但隐藏域的值每次都不同,因为每访问一次页面,都会重新加密一次,虽然AntiForgeryToken对象的值相同,但通过MachineKey的Protect加密后,每次加密的值都会不同。 
AntiForgery使用MachineKey进行加密,所以如果系统使用负载均衡,就需要配置MachineKey,否则不同服务器的MachineKey不同,导致无法解密。

[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model)
{//ect.
}

在执行@Html.AntiForgeryToken()语句时,会调用GetHtml方法。GetHtml方法中会调用GetFormInputElement方法,该方法会在cookie中写入加密后的数据,并返回Html标签代码。该标签代码会写入到页面中.

        public static HtmlString GetHtml(){if (HttpContext.Current == null){throw new ArgumentException(WebPageResources.HttpContextUnavailable);}TagBuilder retVal = _worker.GetFormInputElement(new HttpContextWrapper(HttpContext.Current));return retVal.ToHtmlString(TagRenderMode.SelfClosing);}

在GetFormInputElement方法中,首先通过GetCookieTokenNoThrow方法获取Cookie中AntiForgeryToken对象(第一访问页面该对象为空)。再通过GetTokens方法获取新的newCookieToken以及formToken(newCookieToken就是写入cookie的token,formToken就是写入隐藏域的token)。如果oldCookieToken不为空,那么newCookieToken就会为空,这样就不会重新写入cookie。所以同一个会话期间cookie值会相同。如果不为空就通过SaveCookieToken方法写入cookie。

        public TagBuilder GetFormInputElement(HttpContextBase httpContext){CheckSSLConfig(httpContext);AntiForgeryToken oldCookieToken = GetCookieTokenNoThrow(httpContext);AntiForgeryToken newCookieToken, formToken;GetTokens(httpContext, oldCookieToken, out newCookieToken, out formToken);if (newCookieToken != null){// If a new cookie was generated, persist it._tokenStore.SaveCookieToken(httpContext, newCookieToken);}if (!_config.SuppressXFrameOptionsHeader){// Adding X-Frame-Options header to prevent ClickJacking. See// http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10// for more information.httpContext.Response.AddHeader("X-Frame-Options", "SAMEORIGIN");}// <input type="hidden" name="__AntiForgeryToken" value="..." />TagBuilder retVal = new TagBuilder("input");retVal.Attributes["type"] = "hidden";retVal.Attributes["name"] = _config.FormFieldName;retVal.Attributes["value"] = _serializer.Serialize(formToken);return retVal;}

SaveCookieToken方法先通过Serialize方法序列化,序列化的时候会对数据加密,再写入cookie

        public void SaveCookieToken(HttpContextBase httpContext, AntiForgeryToken token){string serializedToken = _serializer.Serialize(token);HttpCookie newCookie = new HttpCookie(_config.CookieName, serializedToken){HttpOnly = true};// Note: don't use "newCookie.Secure = _config.RequireSSL;" since the default// value of newCookie.Secure is automatically populated from the <httpCookies>// config element.if (_config.RequireSSL){newCookie.Secure = true;}httpContext.Response.Cookies.Set(newCookie);}

GetTokens方法,如果oldCookieToken不为空,就不重新生成newCookieToken。为空则通过GenerateCookieToken方法生成一个Token。再调用GenerateFormToken方法生成formToken

        private void GetTokens(HttpContextBase httpContext, AntiForgeryToken oldCookieToken, out AntiForgeryToken newCookieToken, out AntiForgeryToken formToken){newCookieToken = null;if (!_validator.IsCookieTokenValid(oldCookieToken)){// Need to make sure we're always operating with a good cookie token.oldCookieToken = newCookieToken = _validator.GenerateCookieToken();}Contract.Assert(_validator.IsCookieTokenValid(oldCookieToken));formToken = _validator.GenerateFormToken(httpContext, ExtractIdentity(httpContext), oldCookieToken);}

GenerateCookieToken方法生成cookieToken,即创建一个新的AntiForgeryToken对象。AntiForgeryToken有个SecurityToken属性,类型为BinaryBlob。BianryBlob对象会通过RNGCryptoServiceProvider实例的GetBytes方法填充强随机序列。填充的序列就是用来验证的随机数。即随机数是在创建AntiForgeryToken对象时自动生成的

        public AntiForgeryToken GenerateCookieToken(){return new AntiForgeryToken(){// SecurityToken will be populated automatically.IsSessionToken = true};}

GenerateFormToken方法,就是把cookieToken的SecurityToken赋值给formToken。这样就会使得cookieToken与formToken的SecurityToken值相等

        public AntiForgeryToken GenerateFormToken(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken cookieToken){Contract.Assert(IsCookieTokenValid(cookieToken));AntiForgeryToken formToken = new AntiForgeryToken(){SecurityToken = cookieToken.SecurityToken,IsSessionToken = false};bool requireAuthenticatedUserHeuristicChecks = false;// populate Username and ClaimUidif (identity != null && identity.IsAuthenticated){if (!_config.SuppressIdentityHeuristicChecks){// If the user is authenticated and heuristic checks are not suppressed,// then Username, ClaimUid, or AdditionalData must be set.requireAuthenticatedUserHeuristicChecks = true;}formToken.ClaimUid = _claimUidExtractor.ExtractClaimUid(identity);if (formToken.ClaimUid == null){formToken.Username = identity.Name;}}// populate AdditionalDataif (_config.AdditionalDataProvider != null){formToken.AdditionalData = _config.AdditionalDataProvider.GetAdditionalData(httpContext);}if (requireAuthenticatedUserHeuristicChecks&& String.IsNullOrEmpty(formToken.Username)&& formToken.ClaimUid == null&& String.IsNullOrEmpty(formToken.AdditionalData)){// Application says user is authenticated, but we have no identifier for the user.throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,WebPageResources.TokenValidator_AuthenticatedUserWithoutUsername, identity.GetType()));}return formToken;}

生成cookieToken和formToken后就会调用Serialize方法进行序列化。序列化的时候会调用MachineKey的Protect方法进行加密。每次加密后的值都不相同。如果使用了负载均衡,一定要配置MachineKey,而不能使用系统的值

 public string Serialize(AntiForgeryToken token){Contract.Assert(token != null);using (MemoryStream stream = new MemoryStream()){using (BinaryWriter writer = new BinaryWriter(stream)){writer.Write(TokenVersion);writer.Write(token.SecurityToken.GetData());writer.Write(token.IsSessionToken);if (!token.IsSessionToken){if (token.ClaimUid != null){writer.Write(true /* isClaimsBased */);writer.Write(token.ClaimUid.GetData());}else{writer.Write(false /* isClaimsBased */);writer.Write(token.Username);}writer.Write(token.AdditionalData);}writer.Flush();return _cryptoSystem.Protect(stream.ToArray());}}}

ASP.NET MVC 使用防伪造令牌来避免CSRF攻击相关推荐

  1. 我要学ASP.NET MVC 3.0(十三): MVC 3.0 防止跨站点请求伪造 (CSRF) 攻击

    我要学ASP.NET MVC 3.0(十三): MVC 3.0 防止跨站点请求伪造 (CSRF) 攻击 概述      众所周知,ASP.Net MVC程序在浏览器运行时产生了标准的Html标签,包括 ...

  2. ASP.NET MVC 5 - 验证编辑方法(Edit method)和编辑视图(Edit view)

    在本节中,您将验证电影控制器生成的编辑方法(Edit action methods)和视图.但是首先将修改点代码,使得发布日期属性(ReleaseDate)看上去更好.打开Models \ Movie ...

  3. 认识ASP.NET MVC的5种AuthorizationFilter

    在总体介绍了筛选器及其提供机制(<深入探讨ASP.NET MVC的筛选器>)之后,我们按照执行的先后顺序对四种不同的筛选器进行单独介绍,首先来介绍最先执行的AuthorizationFil ...

  4. ASP.NET MVC中的安全性

    目录 介绍 认证 表单身份验证 Windows身份验证 如何配置表单身份验证? 我们如何使用Windows身份验证进行身份验 XSS Anti XSS Library 跨站点请求伪造 那问题是什么? ...

  5. asp html表单没有csrf保护,ASP.NET MVC 和网页中的 XSRF/CSRF 防护

    ASP.NET MVC 和网页中的 XSRF/CSRF 防护 03/14/2013 本文内容 跨站点请求伪造(也称为 XSRF 或 CSRF)是一种针对 Web 托管型应用程序的攻击,恶意网站凭此可以 ...

  6. Asp.net MVC 3 防止 Cross-Site Request Forgery (CSRF)原理及扩展 安全 注入

    原理:http://blog.csdn.net/cpytiger/article/details/8781457 原文地址:http://www.cnblogs.com/wintersun/archi ...

  7. 成员资格、授权 – ASP.NET MVC 4 系列

    ASP.NET MVC 不像 ASP.NET WEB FORMS 那样提供了很多自动保护机制来保护页面不受恶意用户的攻击,更明确的说,后者是致力于使应用程序免受攻击: 服务器组件对显示的值和特性进行 ...

  8. apache2.4.9 开启path_info访问_【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)...

    新建项目 打开VS2015,找到菜单项[文件->新建->项目],打开向导对话框: 注意我们的选择项: 运行平台:.NET FrameWork 4.5 项目模板:ASP.NET Web Ap ...

  9. 《Pro ASP.NET MVC 3 Framework》学习笔记之五【依赖注入及ninject工具使用】

    一,创建松耦合的组件 1."分解关注点"是MVC模式里面一个非常重要的特性.我们想要在应用程序里面创建的组件尽可能的独立,这样我们就能管理比较少的依赖关系.理想情况下,每个组件都是 ...

最新文章

  1. cocos2dx对所有子节点设置透明度
  2. 清华大学图神经网络综述:模型与应用
  3. 根据json文件读取json信息
  4. js高级—tab栏切换(面向对象做法)
  5. 光纤接口怎么接 图解_光纤的数据比网线快很多倍,但为什么没有在家庭局域网中普及呢?...
  6. 如何学习前端 转载
  7. mysql5.5中的MaxValue关键字
  8. Linux 下几款程序内存泄漏检查工具
  9. node本地连接服务器的数据库_Linux本地连接阿里云服务器,以及下载node.js配置环境...
  10. vue监听路由的变化,跳转到同一个页面时,Url改变但视图未重新加载问题
  11. POJ 3308 最少点集覆盖
  12. Matlab 2016 超详细安装教程
  13. 博弈论python实例_博弈论读书笔记(七)贝叶斯博弈举例和显示原理
  14. 使用NetMHCpan进行肿瘤新抗原预测分析
  15. linux 内核usleep,linux 内核 usleep
  16. 中小型电子商务网站架构
  17. 用VB开发USB接口POS打印机进行打印和弹钱箱
  18. python应用——将raw文件转化为jpg文件,并显示图像
  19. 【you may need to restart the kernel to use updated packages】
  20. matlab语音转换文字,语音转换 有没有做这方面的大侠,大家一起来交流一下经验...

热门文章

  1. 怎么将oracle的sql文件转换成mysql的sql文件
  2. 如何设制 select 不可编辑 只读
  3. [Android疑难杂症]Gallery默认和横竖屏切换选中状态问题(2.3、2.3系统存在)
  4. 用Dreamweaver实现ASP动态网站建设【8】
  5. 路由重分布列表控制例子
  6. OSPF单域实验报告
  7. oracle权限培训,Java培训-ORACLE数据库学习【2】用户权限
  8. haarcascades---各种分类器xml文件下载地址
  9. Django web开发笔记
  10. [转载] Java中Scanner用法总结