前言

前几天,有个朋友问我关于AntiForgeryToken问题,由于对这一块的理解也并不深入,所以就去研究了一番,梳理了一下。

在梳理之前,还需要简单了解一下背景知识。

AntiForgeryToken 可以说是处理/预防CSRF的一种处理方案。

那么什么是CSRF呢?

CSRF(Cross-site request forgery)是跨站请求伪造,也被称为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。

简单理解的话就是:有人盗用了你的身份,并且用你的名义发送恶意请求

最近几年,CSRF处于不温不火的地位,但是还是要对这个小心防范!

更加详细的内容可以参考维基百科:Cross-site request forgery

下面从使用的角度来分析一下CSRF在 ASP.NET Core中的处理,个人认为主要有下面两大块

  • 视图层面

  • 控制器层面

视图层面

用法

@Html.AntiForgeryToken()

在视图层面的用法相对比较简单,用的还是HtmlHelper的那一套东西。在Form表单中加上这一句就可以了。

原理浅析

当在表单中添加了上面的代码后,页面会生成一个隐藏域,隐藏域的值是一个生成的token(防伪标识),类似下面的例子

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8FBn4LzSYglJpE6Q0fWvZ8WDMTgwK49lDU1XGuP5-5j4JlSCML_IDOO3XDL5EOyI_mS2Ux7lLSfI7ASQnIIxo2ScEJvnABf9v51TUZl_iM2S63zuiPK4lcXRPa_KUUDbK-LS4HD16pJusFRppj-dEGc" />

其中的name="__RequestVerificationToken"是定义的一个const变量,value=XXXXX是根据一堆东西进行base64编码,并对base64编码后的内容进行简单处理的结果,具体的实现可以参见Base64UrlTextEncoder.cs

生成上面隐藏域的代码在AntiforgeryExtensions这个文件里面,github上的源码文件:AntiforgeryExtensions.cs

其中重点的方法如下:

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("\" />");
}

相当的清晰明了!

控制器层面

用法

[ValidateAntiForgeryToken]
[AutoValidateAntiforgeryToken]
[IgnoreAntiforgeryToken]

这三个都是可以基于类或方法的,所以我们只要在某个控制器或者是在某个Action上面加上这些Attribute就可以了。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]

原理浅析

本质是Filter(过滤器),验证上面隐藏域的value

过滤器实现:ValidateAntiforgeryTokenAuthorizationFilterAutoValidateAntiforgeryTokenAuthorizationFilter

其中 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源码:ValidateAntiforgeryTokenAuthorizationFilter.cs

当然这里的过滤器只是一个入口,相关的验证并不是在这里实现的。而是在Antiforgery这个项目上,其实说这个模块可能会更贴切一些。

由于是面向接口的编程,所以要知道具体的实现,就要找到对应的实现类才可以。

Antiforgery这个项目中,有这样一个扩展方法AntiforgeryServiceCollectionExtensions,里面告诉了我们相对应的实现是DefaultAntiforgery这个类。其实Nancy的源码看多了,看一下类的命名就应该能知道个八九不离十。

  services.TryAddSingleton<IAntiforgery, DefaultAntiforgery>();

其中还涉及到了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 (!cookieToken.IsCookieToken || requestToken.IsCookieToken){message = Resources.AntiforgeryToken_TokensSwapped;        return false;}    // Are the security tokens embedded in each incoming token identical?if (!object.Equals(cookieToken.SecurityToken, requestToken.SecurityToken)){message = Resources.AntiforgeryToken_SecurityTokenMismatch;        return false;}    // Is the incoming 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 comparer = StringComparer.OrdinalIgnoreCase;    if (currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase)){comparer = StringComparer.Ordinal;}    if (!comparer.Equals(requestToken.Username, currentUsername)){message = Resources.FormatAntiforgeryToken_UsernameMismatch(requestToken.Username, currentUsername);        return false;}    if (!object.Equals(requestToken.ClaimUid, currentClaimUid)){message = Resources.AntiforgeryToken_ClaimUidMismatch;        return false;}    // Is the AdditionalData valid?if (_additionalDataProvider != null &&!_additionalDataProvider.ValidateAdditionalData(httpContext, requestToken.AdditionalData)){message = Resources.AntiforgeryToken_AdditionalDataCheckFailed;        return false;}message = null;    return true;
}

注:验证前还有一个反序列化的过程,这个反序列化就是从Cookie中拿到要判断的cookietoken和requesttoken

如何使用

前面粗略介绍了一下其内部的实现,下面再用个简单的例子来看看具体的使用情况:

使用一:常规的Form表单

先在视图添加一个Form表单

<form id="form1" action="/home/antiform" method="post">    @Html.AntiForgeryToken()    <p><input type="text" name="message" /></p>    <p><input type="submit" value="Send by Form" /></p></form>

在控制器添加一个Action

[ValidateAntiForgeryToken]
[HttpPost]public IActionResult AntiForm(string message){    return Content(message);
}

来看看生成的html是不是如我们前面所说,将@Html.AntiForgeryToken()输出为一个name为__RequestVerificationToken的隐藏域:

再来看看cookie的相关信息:

可以看到,一切都还是按照前面所说的执行。在输入框输入信息并点击按钮也能正常显示我们输入的文字。

使用二:Ajax提交

表单:

<form id="form2" action="/home/antiajax" method="post">    @Html.AntiForgeryToken()    <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();                });
})

这样子的写法也是和上面的结果是一样的!

怕的是出现下面这样的写法:

$.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",dataType: "html",url: '@Url.Action("AntiAjax", "Home")',data: { message: $('#ajaxMsg').val() },headers:{            "RequestVerificationToken": token},success: function (result){alert(result);},error: function (err, 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",dataType: "html",url: '@Url.Action("AntiAjax", "Home")',data: { message: $('#ajaxMsg').val() },headers:{        "CUSTOMER-CSRF-HEADER": token //注意header要修改},success: function (result){alert(result);},error: function (err, scnd){alert(err.statusText);}
});

下面是效果:

Form表单:

Cookie:

本文涉及到的相关项目:

  • Mvc

  • Antiforgery

  • HttpAbstractions

关于CSRF相关的内容

Preventing Cross-Site Request Forgery (XSRF/CSRF) Attacks in ASP.NET Core

浅谈CSRF攻击方式

原文地址:http://www.cnblogs.com/catcher1994/p/6720212.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

初探CSRF在ASP.NET Core中的处理方式相关推荐

  1. 在 ASP.NET Core 中使用多种方式给 Action 传参

    ASP.NET Core 是一个跨平台,开源的,轻量级,高性能 并且 高度模块化的web框架.在 ASP.NET Core MVC 中有很多种方式可以给 Action 方法传递参数,比如说:url方式 ...

  2. ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态

    原文:Managing Application State 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩 在 ASP.NET Core 中,有多种途径可以对应用程序的状态进行 ...

  3. 如何使用C#在ASP.NET Core中轻松实现QRCoder

    by Yogi 由瑜伽士 如何使用C#在ASP.NET Core中轻松实现QRCoder (How to easily implement QRCoder in ASP.NET Core using ...

  4. asp.net core中IHttpContextAccessor和HttpContextAccessor的妙用

    分享一篇文章,关于asp.net core中httpcontext的拓展. 现在,试图围绕HttpContext.Current构建你的代码真的不是一个好主意,但是我想如果你正在迁移一个企业类型的应用 ...

  5. 在asp.net core中使用托管服务实现后台任务

    在业务场景中经常需要后台服务不停的或定时处理一些任务,这些任务是不需要及时响应请求的. 在 asp.net中会使用windows服务来处理. 在 asp.net core中,可以使用托管服务来实现,托 ...

  6. ASP.NET Core中显示自定义错误页面-增强版

    之前的博文 ASP.NET Core中显示自定义错误页面 中的方法是在项目中硬编码实现的,当有多个项目时,就会造成不同项目之间的重复代码,不可取. 在这篇博文中改用middleware实现,并且放在独 ...

  7. 探索ASP.NET Core中的IStartupFilter

    原文:Exploring IStartupFilter in ASP.NET Core 作者:Andrew Lock 译者:Lamond Lu 在本篇博客中,我将介绍一下IStartupFilter, ...

  8. 如何简单的在 ASP.NET Core 中集成 JWT 认证?

    前情提要:ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统 文章超长预警(1万字以上),不想看全部实现过程的同学可以直接跳转到末尾查看成果或者一键安装相关的 nuget 包 自上一 ...

  9. 在 ASP.NET Core 中集成 Skywalking APM

    前言 大家好,今天给大家介绍一下如何在 ASP.NET Core 项目中集成 Skywalking,Skywalking 是 Apache 基金会下面的一个开源 APM 项目,有些同学可能会 APM ...

最新文章

  1. C语言中文件的读写(fputc和fgetc)(putchar和getchar)
  2. 精通JavaScript--07设计模式:行为型
  3. cd linux menu.lst,用ultraiso制作cdlinux的U盘启动盘
  4. hdu 5108(分解质因数)
  5. Http benchmarking 工具 wrk 基本使用
  6. 快速搭建Springboot项目的两种方式!!
  7. mysql npe问题_MySQL为Null会导致5个问题,个个致命!
  8. Qt工作笔记-使用qrand与QTime产生随机数
  9. 团队管理(3)---团队管理方法
  10. html密码字段语法,HTML input 标签的 type 属性
  11. python函数转换_将Python函数转换为PL/Python函数
  12. 百度搜索引擎关键字URL采集爬虫优化行业定投方案高效获得行业流量-笔记篇
  13. MySQL 一条SQL语句执行得很慢的原因有哪些?
  14. C程序设计--命令行参数(argc 和 argv[])
  15. php 伪静态 获取当前页面路径_php url伪静态过程详解
  16. IOS开发学习笔记(一)——ObjectC语言快速入门
  17. Centos中安装svnserver
  18. CuteFTP Pro 8.3.4 最新破解绿色版
  19. 轻松搞定——JAVA实现DOC转PDF功能
  20. 网站分析平台:是选择百度统计,还是 Google Analytics 呢?

热门文章

  1. 前端几个笔试题及答案(bd)
  2. C++学习之路: 线程封装(基于对象编程)
  3. (装载)C#中AppDomain.CurrentDomain.BaseDirectory与Application.StartupPath的区别
  4. 网管心得:优化网络性能给局域网提速[好文章]
  5. C# 离线人脸识别 ArcSoft
  6. 开源与自由 | 商业自由:从边缘到核心贡献
  7. 一条nginx命令引发的对于容器的思考
  8. 基于ABP落地领域驱动设计-01.全景图
  9. ML.NET Cookbook:(16)什么是规范化?为什么我需要关心?
  10. WPF 读取Docx文件并显示(附下载链接)