<h2 ID="前言">前言
前几天,有个朋友问我关于AntiForgeryToken问题,由于对这一块的理解也并不深入,所以就去研究了一番,梳理了一下。
在梳理之前,还需要简单了解一下背景知识。
AntiForgeryToken 可以说是处理/预防CSRF的一种处理方案。
那么什么是CSRF呢?
CSRF(Cross-site request forgery)是跨站请求伪造,也被称为One Click Attack或者Session RIDing,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。
简单理解的话就是:有人盗用了你的身份,并且用你的名义发送恶意请求。
最近几年,CSRF处于不温不火的地位,但是还是要对这个小心防范!
更加详细的内容可以参考维基百科:
下面从使用的角度来分析一下CSRF在 ASP.NET Core中的处理,个人认为主要有下面两大块
视图层面控制器层面@Html.AntiForgeryToken()
在视图层面的用法相对比较简单,用的还是HtmlHelper的那一套东西。在Form表单中加上这一句就可以了。
当在表单中添加了上面的代码后,页面会生成一个隐藏域,隐藏域的值是一个生成的token(防伪标识),类似下面的例子
其中的name="__RequestVerificationToken"
是定义的一个const变量,value=XXXXX
是根据一堆东西进行base64编码,并对base64编码后的内容进行简单处理的结果,具体的实现可以参见
生成上面隐藏域的代码在AntiforgeryExtensions这个文件里面,github上的源码文件:
其中重点的方法如下:
public void WriteTo(TextWriter writer,HtmlEncoder encoder){ writer.Write("");}
相当的清晰明了!
[ValidateAntiForgeryToken][AutoValidateAntiforgeryToken][IgnoreAntiforgeryToken]
这三个都是可以基于类或方法的,所以我们只要在某个控制器或者是在某个Action上面加上这些Attribute就可以了。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,AllowMultiple = false,Inherited = true)]
本质是Filter(过滤器),验证上面隐藏域的value
过滤器实现:ValidateAntiforgeryTokenAuthorizationFilter
和AutoValidateAntiforgeryTokenAuthorizationFilter
其中 AutoValidateAntiforgeryTokenAuthorizationFilter是继承了ValidateAntiforgeryTokenAuthorizationFilter,只重写了其中的ShouldValidate方法。
下面贴出ValidateAntiforgeryTokenAuthorizationFilter的核心方法:
public class ValidateAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter,IAntiforgeryPolicy{ public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (IsClosestAntiforgeryPolicy(context.Filters) && ShouldValidate(context)) { try { await _antiforgery.ValidateRequestAsync(context.HttpContext); } catch (AntiforgeryValidationException exception) { _logger.AntiforgeryTokenInvalid(exception.Message,exception); context.Result = new BadRequestResult(); } }}
}
完整实现可参见github源码:
当然这里的过滤器只是一个入口,相关的验证并不是在这里实现的。而是在Antiforgery这个项目上,其实说这个模块可能会更贴切一些。
由于是面向接口的编程,所以要知道具体的实现,就要找到对应的实现类才可以。
在Antiforgery这个项目中,有这样一个扩展方法AntiforgeryServiceCollectionExtensions,里面告诉了我们相对应的实现是DefaultAntiforgery这个类。其实Nancy的源码看多了,看一下类的命名就应该能知道个八九不离十。
services.TryAddSingleton();
其中还涉及到了IServiceCollection,但这不是本文的重点,所以不会展开讲这个,只是提出它在 .net core中是一个重要的点。
好了,回归正题!要验证是否是合法的请求,自然要先拿到要验证的内容。
var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
它是从cookie中拿到一个指定的前缀为.AspNetCore.Antiforgery.
的cookie,并根据这个cookie进行后面相应的判断。下面是验证的具体实现:
public bool TryValIDatetokenSet( httpContext httpContext,AntiforgeryToken cookieToken,AntiforgeryToken requestToken,out string message){ //去掉了部分非空的判断// Do the tokens have the correct format?if (!<a href="https://m.jb51.cc/tag/cookie/" target="_blank" >cookie</a>Token.Is<a href="https://m.jb51.cc/tag/cookie/" target="_blank" >cookie</a>Token || requestToken.Is<a href="https://m.jb51.cc/tag/cookie/" target="_blank" >cookie</a>Token){ message = Resources.AntiforgeryToken_TokensSwapped; return false;}// Are the s<a href="https://www.jb51.cc/tag/ecurity/" target="_blank" >ecurity</a> tokens em<a href="https://www.jb51.cc/tag/bed/" target="_blank" >bed</a>ded in each incoming token <a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>entical?if (!object.Equals(<a href="https://m.jb51.cc/tag/cookie/" target="_blank" >cookie</a>Token.S<a href="https://www.jb51.cc/tag/ecurity/" target="_blank" >ecurity</a>Token,requestToken.S<a href="https://www.jb51.cc/tag/ecurity/" target="_blank" >ecurity</a>Token)){ message = Resources.AntiforgeryToken_S<a href="https://www.jb51.cc/tag/ecurity/" target="_blank" >ecurity</a>TokenMismatch; return false;}// Is the incoming token meant for the current user?var currentUser<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a> = string.Empty;BinaryBlob currentCl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a> = n<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>l;var authenticated<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>entity = GetAuthenticated<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>entity(<a href="https://m.jb51.cc/tag/http/" target="_blank" >http</a>Context.User);if (authenticated<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>entity != n<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>l){ currentCl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a> = GetCl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>Blob(_cl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>Extractor.ExtractCl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>(<a href="https://m.jb51.cc/tag/http/" target="_blank" >http</a>Context.User)); if (currentCl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a> == n<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>l) { currentUser<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a> = authenticated<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>entity.<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a> ?? string.Empty; }}// Open<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a> and other similar authentication schemes use URIs for the user<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a>.// These sho<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>d be treated as case-sensitive.var comparer = StringComparer.Ordina<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>g<a href="https://www.jb51.cc/tag/nor/" target="_blank" >nor</a>eCase;if (currentUser<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a>.StartsWith("<a href="https://m.jb51.cc/tag/http/" target="_blank" >http</a>://",StringComparison.Ordina<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>g<a href="https://www.jb51.cc/tag/nor/" target="_blank" >nor</a>eCase) || currentUser<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a>.StartsWith("<a href="https://m.jb51.cc/tag/http/" target="_blank" >http</a>s://",StringComparison.Ordina<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>g<a href="https://www.jb51.cc/tag/nor/" target="_blank" >nor</a>eCase)){ comparer = StringComparer.Ordinal;}if (!comparer.Equals(requestToken.User<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a>,currentUser<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a>)){ message = Resources.Form<a href="https://www.jb51.cc/tag/atan/" target="_blank" >atan</a>tiforgeryToken_User<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a>Mismatch(requestToken.User<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a>,currentUser<a href="https://m.jb51.cc/tag/name/" target="_blank" >name</a>); return false;}if (!object.Equals(requestToken.Cl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>,currentCl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>)){ message = Resources.AntiforgeryToken_Cl<a href="https://www.jb51.cc/tag/aim/" target="_blank" >aim</a>U<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>Mismatch; return false;}// Is the AdditionalData val<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>?if (_additionalDataProv<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>er != n<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>l && !_additionalDataProv<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>er.Val<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>ateAdditionalData(<a href="https://m.jb51.cc/tag/http/" target="_blank" >http</a>Context,requestToken.AdditionalData)){ message = Resources.AntiforgeryToken_AdditionalDataCheck<a href="https://www.jb51.cc/tag/Failed/" target="_blank" >Failed</a>; return false;}message = n<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>l;return true;
}
注:验证前还有一个反序列化的过程,这个反序列化就是从cookie中拿到要判断的cookietoken和requesttoken
前面粗略介绍了一下其内部的实现,下面再用个简单的例子来看看具体的使用情况:
先在视图添加一个Form表单
在控制器添加一个Action
[ValIDateAntiForgeryToken][httpPost]public IActionResult AntiForm(string message){ return Content(message);}
来看看生成的HTML是不是如我们前面所说,将@HTML.AntiForgeryToken()
输出为一个name为__RequestVerificationToken
的隐藏域:
再来看看cookie的相关信息:
可以看到,一切都还是按照前面所说的执行。在输入框输入信息并点击按钮也能正常显示我们输入的文字。
表单:
Js:
$(function () { $("#btnAJAX").on("click",function () { $("#form2").submit(); });})
这样子的写法也是和上面的结果是一样的!
怕的是出现下面这样的写法:
$.AJAX({ type: "post",dataType: "HTML",url: '@Url.Action("AntiAJAX","Home")',data: { message: $('#AJAXMsg').val() },success: function (result) { alert(result); },error: function (err,scnd) { alert(err.statusText); }});
这样,正常情况下确实是看不出任何毛病,但是实际确是下面的结果(400错误):
相信大家也都发现了问题的所在了!!隐藏域的相关内容并没有一起post过去!!
处理方法有两种:
方法一:
在data中加上隐藏域相关的内容,大致如下:
$.AJAX({ // data: { message: $('#AJAXMsg').val(),__RequestVerificationToken: $("input[name='__RequestVerificationToken']").val()}});
方法二:
在请求中添加一个header
$("#btnAJAX").on("click",function () { var token = $("input[name='__RequestVerificationToken']").val(); $.AJAX({ type: "post",headers: { "RequestVerificationToken": token },success: function (result) { alert(result); },scnd) { alert(err.statusText); } });});
这样就能处理上面出现的问题了!
可能会有不少人觉得,像那个生成的隐藏域那个name能不能换成自己的,那个cookie的名字能不能换成自己的〜〜
答案是肯定可以的,下面简单示范一下:
在Startup的ConfigureServices方法中,添加下面的内容即可对默认的名称进行相应的修改。
services.AddAntiforgery(option =>{ option.cookiename = "CUSTOMER-CSRF-cookie"; option.FormFIEldname = "CustomerFIEldname"; option.headername = "CUSTOMER-CSRF-header";});
相应的,AJAX请求也要做修改:
var token = $("input[name='CustomerFIEldname']").val();//隐藏域的名称要改$.AJAX({ type: "post",headers: { "CUSTOMER-CSRF-header": token //注意header要修改 },scnd) { alert(err.statusText); }});
下面是效果:
Form表单:
cookie:
href="http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.HTML">浅谈CSRF攻击方式
总结以上是内存溢出为你收集整理的初探CSRF在ASP.NET Core中的处理方式全部内容,希望文章能够帮你解决初探CSRF在ASP.NET Core中的处理方式所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)