CSRF在ASP.NET Core中的处理方法详解
前言
前几天,有个朋友问我关于AntiFeryToken问题,由于对这一块的理解也并不深入,所以就去研究了一番,梳理了一下。
在梳理之前,还需要简单了解一下背景知识。
AntiFeryToken 可以说是处理/预防CSRF的一种处理方案。
那么什么是CSRF呢?
CSRF(Cross-site request fery)是跨站请求伪造,也被称为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。
简单理解的话就是有人盗用了你的身份,并且用你的名义发送恶意请求。
最近几年,CSRF处于不温不火的地位,还是要对这个小心防范!
更加详细的内容可以参考维基百科
狼蚁网站SEO优化从使用的角度来分析一下CSRF在 ASP.NET Core中的处理,个人认为主要有狼蚁网站SEO优化两大块
- 视图层面
- 控制器
层面视图层面
用法
@Html.AntiFeryToken()
在视图层面的用法相对比较简单,用的还是HtmlHelper的那一套东西。在Form表单中加上这一句就可以了。
原理浅析
当在表单中添加了上面的代码后,页面会生成一个隐藏域,隐藏域的值是一个生成的token(防伪标识),类似狼蚁网站SEO优化的例子
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8FBn4LzSYglJpE6Q0fWvZ8WDMTgwK49lDU1XGuP5-5j4JlSCML_IDOO3XDL5EOyI_mS2Ux7lLSfI7ASQnIIxo2ScEJvnABf9v51TUZl_iM2S63zuiPK4lcXRPa_KUUDbK-LS4HD16pJusFRppj-dEGc" />
其中的name="__RequestVerificationToken"
是定义的一个const变量,value=XXXXX
是根据一堆东西进行base64编码,并对base64编码后的内容进行简单处理的结果,具体的实现可以参见
生成上面隐藏域的代码在AntiferyExtensions这个文件里面,github上的源码文件
其中重点的方法如下
public void WriteTo(TextWriter writer, HtmlEncoder encoder) { writer.Write("<input name=\""); encoder.Encode(writer, _fieldName); writer.Write("\" type=\"hidden\" value=\""); encoder.Encode(writer, _requestToken); writer.Write("\" />"); }
相当的清晰明了!
控制器层面
用法
[ValidateAntiFeryToken]
[AutoValidateAntiferyToken]
[IgnoreAntiferyToken]
这三个都是可以基于类或方法的,所以我们只要在某个控制器或者是在某个Action上面加上这些Attribute就可以了。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
原理浅析
本质是Filter(过滤器),验证上面隐藏域的value
过滤器实现ValidateAntiferyTokenAuthorizationFilter和AutoValidateAntiferyTokenAuthorizationFilter
其中 AutoValidateAntiferyTokenAuthorizationFilter是继承了ValidateAntiferyTokenAuthorizationFilter,只重写了其中的ShouldValidate方法。
狼蚁网站SEO优化贴出ValidateAntiferyTokenAuthorizationFilter的核心方法
public class ValidateAntiferyTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiferyPolicy { public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (IsClosestAntiferyPolicy(context.Filters) && ShouldValidate(context)) { try { await _antifery.ValidateRequestAsync(context.HttpContext); } catch (AntiferyValidationException exception) { _logger.AntiferyTokenInvalid(exception.Message, exception); context.Result = new BadRequestResult(); } } } }
完整实现可参见github源码
这里的过滤器只是一个入口,相关的验证并不是在这里实现的。而是在Antifery这个项目上,其实说这个模块可能会更贴切一些。
由于是面向接口的编程,所以要知道具体的实现,就要找到对应的实现类才可以。
在Antifery这个项目中,有这样一个扩展方法AntiferyServiceCollectionExtensions,里面告诉了我们相对应的实现是DefaultAntifery这个类。其实Nancy的源码看多了,看一下类的命名就应该能知道个八九不离十。
services.TryAddSingleton<IAntifery, DefaultAntifery>();
其中还涉及到了IServiceCollection,但这不是本文的重点,所以不会展开讲这个,只是提出它在 . core中是一个重要的点。
好了,回归正题!要验证是否是合法的请求,自然要先拿到要验证的内容。
var tokens = await _tokenStore.GetRequestTokensAsync(httpContext);
它是从Cookie中拿到一个指定的前缀为.AspNetCore.Antifery.的Cookie,并根据这个Cookie进行后面相应的判断。狼蚁网站SEO优化是验证的具体实现
public bool TryValidateTokenSet( HttpContext httpContext, AntiferyToken cookieToken, AntiferyToken requestToken, out string message) { //去掉了部分非空的判断 // Do the tokens have the correct format? if (!cookieToken.IsCookieToken || requestToken.IsCookieToken) { message = Resources.AntiferyToken_TokensSwapped; return false; } // Are the security tokens embedded in each ining token identical? if (!object.Equals(cookieToken.SecurityToken, requestToken.SecurityToken)) { message = Resources.AntiferyToken_SecurityTokenMismatch; return false; } // Is the ining token meant for the current user? var currentUsername = string.Empty; BinaryBlob currentClaimUid = null; var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User); if (authenticatedIdentity != null) { currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User)); if (currentClaimUid == null) { currentUsername = authenticatedIdentity.Name ?? string.Empty; } } // OpenID and other similar authentication schemes use URIs for the username. // These should be treated as case-sensitive. var parer = StringComparer.OrdinalIgnoreCase; if (currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { parer = StringComparer.Ordinal; } if (!parer.Equals(requestToken.Username, currentUsername)) { message = Resources.FormatAntiferyToken_UsernameMismatch(requestToken.Username, currentUsername); return false; } if (!object.Equals(requestToken.ClaimUid, currentClaimUid)) { message = Resources.AntiferyToken_ClaimUidMismatch; return false; } // Is the AdditionalData valid? if (_additionalDataProvider != null && !_additionalDataProvider.ValidateAdditionalData(httpContext, requestToken.AdditionalData)) { message = Resources.AntiferyToken_AdditionalDataCheckFailed; return false; } message = null; return true; }
注验证前还有一个反序列化的过程,这个反序列化就是从Cookie中拿到要判断的cookietoken和requesttoken
如何使用
前面粗略介绍了一下其内部的实现,狼蚁网站SEO优化再用个简单的例子来看看具体的使用情况
使用一常规的Form表单
先在视图添加一个Form表单
<form id="form1" action="/home/antiform" method="post"> @Html.AntiFeryToken() <p><input type="text" name="message" /></p> <p><input type="submit" value="Send by Form" /></p> </form>
在控制器添加一个Action
[ValidateAntiFeryToken] [HttpPost] public IActionResult AntiForm(string message) { return Content(message); }
来看看生成的html是不是如我们前面所说,将@Html.AntiFeryToken()
输出为一个name为__RequestVerificationToken
的隐藏域
再来看看cookie的相关信息
可以看到,一切都还是按照前面所说的执行。在输入框输入信息并点击按钮也能正常显示我们输入的文字。
使用二Ajax提交
表单
<form id="form2" action="/home/antiajax" method="post"> @Html.AntiFeryToken() <p><input type="text" name="message" id="ajaxMsg" /></p> <p><input type="button" id="btnAjax" value="Send by Ajax" /></p> </form>
js:
$(function () { $("#btnAjax").on("click", function () { $("#form2").submit(); }); })
这样子的写法也是和上面的结果是一样的!
怕的是出现狼蚁网站SEO优化这样的写法
$.ajax({ type: "post", dataType: "html", url: '@Url.Action("AntiAjax", "Home")', data: { message: $('#ajaxMsg').val() }, suess: function (result) { alert(result); }, error: function (err, sd) { alert(err.statusText); } });
这样,正常情况下确实是看不出任何毛病,实际确是狼蚁网站SEO优化的结果(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", dataType: "html", url: '@Url.Action("AntiAjax", "Home")', data: { message: $('#ajaxMsg').val() }, headers: { "RequestVerificationToken": token }, suess: function (result) { alert(result); }, error: function (err, sd) { alert(err.statusText); } }); });
这样就能处理上面出现的问题了!
使用三自定义相关信息
可能会有不少人觉得,像那个生成的隐藏域那个name能不能换成自己的,那个cookie的名字能不能换成自己的〜〜
答案是肯定可以的,狼蚁网站SEO优化简单示范一下
在Startup的ConfigureServices方法中,添加狼蚁网站SEO优化的内容即可对默认的名称进行相应的修改。
services.AddAntifery(option => { option.CookieName = "CUSTOMER-CSRF-COOKIE"; option.FormFieldName = "CustomerFieldName"; option.HeaderName = "CUSTOMER-CSRF-HEADER"; });
相应的,ajax请求也要做修改
var token = $("input[name='CustomerFieldName']").val();//隐藏域的名称要改 $.ajax({ type: "post", dataType: "html", url: '@Url.Action("AntiAjax", "Home")', data: { message: $('#ajaxMsg').val() }, headers: { "CUSTOMER-CSRF-HEADER": token //注意header要修改 }, suess: function (result) { alert(result); }, error: function (err, sd) { alert(err.statusText); } });
狼蚁网站SEO优化是效果
Form表单
Cookie
本文涉及到的相关项目
关于CSRF相关的内容
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对狼蚁SEO的支持。
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程