AngularJS Web Api AntiForgeryToken CSRF

Posted

技术标签:

【中文标题】AngularJS Web Api AntiForgeryToken CSRF【英文标题】: 【发布时间】:2015-12-04 06:43:27 【问题描述】:

我有一个由 ASP.NET MVC 应用程序托管的 AngularJS 单页应用程序 (SPA)。 后端是 ASP.NET Web Api

我想通过在 ASP.NET MVC 部分生成AntiForgeryToken 来保护它免受 CSRF 攻击,并将其传递给 AngularJS,然后让 Web Api 验证从后续 AngularJS 调用收到的 AntiForgeryToken

“跨站请求伪造 (CSRF) 是一种强制结束的攻击 用户在他们所在的 Web 应用程序上执行不需要的操作 目前已通过身份验证。 CSRF 攻击专门针对 状态改变请求,而不是窃取数据,因为攻击者没有 查看对伪造请求的响应的方法。在一点帮助下 社会工程(例如通过电子邮件或聊天发送链接), 攻击者可能会诱骗 Web 应用程序的用户执行 攻击者选择的动作。如果受害者是普通用户,则 成功的 CSRF 攻击可以强制用户执行状态更改 诸如转移资金、更改电子邮件地址等请求 向前。如果受害者是管理帐户,CSRF 可能会妥协 整个 Web 应用程序。” - 开放网络应用程序安全项目 (OWASP)

【问题讨论】:

【参考方案1】:

添加到服务于 AngularJS SPAASP.NET MVC 视图,比如说Views\Home\Index.cshtml , 生成 AntiForgeryToken.

@Html.AntiForgeryToken();

配置AngularJS 将上面生成的AntiForgeryToken 作为Request Header 传递。

angular.module('app')
.run(function ($http) 
    $http.defaults.headers.common['X-XSRF-Token'] =
        angular.element('input[name="__RequestVerificationToken"]').attr('value');
);

创建自定义 Web API 过滤器 以验证所有非 GET 请求(PUTPATCHPOSTDELETE)。

这假设您的所有GET 请求都是安全的,不需要保护。 如果不是这种情况,请删除 if (actionContext.Request.Method.Method != "GET") 排除项。

using System;
using System.Linq;
using System.Net.Http;
using System.Web.Helpers;
using System.Web.Http.Filters;

namespace Care.Web.Filters

    public sealed class WebApiValidateAntiForgeryTokenAttribute : ActionFilterAttribute
    
        public override void OnActionExecuting(
            System.Web.Http.Controllers.HttpActionContext actionContext)
        
            if (actionContext == null)
            
                throw new ArgumentNullException("actionContext");
            

            if (actionContext.Request.Method.Method != "GET")
            
                var headers = actionContext.Request.Headers;
                var tokenCookie = headers
                    .GetCookies()
                    .Select(c => c[AntiForgeryConfig.CookieName])
                    .FirstOrDefault();

                var tokenHeader = string.Empty;
                if (headers.Contains("X-XSRF-Token"))
                
                    tokenHeader = headers.GetValues("X-XSRF-Token").FirstOrDefault();
                

                AntiForgery.Validate(
                    tokenCookie != null ? tokenCookie.Value : null, tokenHeader);
            

            base.OnActionExecuting(actionContext);
        
    

Global.asax.cs 中将新创建的过滤器注册为全局过滤器。

    private static void RegisterWebApiFilters(HttpFilterCollection filters)
    
        filters.Add(new WebApiValidateAntiForgeryTokenAttribute());
    

或者,如果您不希望全局添加此过滤器,则可以仅将其放在某些 Web API 操作上,例如这样

[WebApiValidateAntiForgeryToken]

当然,这在定义上不太安全,因为您总是有可能忘记将属性应用于需要它的操作。

另外,请注意,您必须拥有 Microsoft.AspNet.WebApi.Core 包才能访问 System.Web.Http 命名空间。您可以使用Install-Package Microsoft.AspNet.WebApi.Core 通过 NuGet 安装它。

这篇文章深受this blog post 的启发。

【讨论】:

很抱歉这个问题,但通常的做法是提出问题并立即给出准备好的答案吗? 是的。您可以在此处阅读更多信息:blog.stackexchange.com/2011/07/…。 @Mihai-AndreiDinculescu 如果页面上有超过 1 个令牌,这仍然有效吗?可能有超过 1 个表单,这意味着超过 1 个带有令牌的隐藏输入。 @onefootswill 是的,很明显。但是为什么你会在一页上有多个令牌?一个人应该满足所有的要求。您无需将其包含在您的表单中。将它放在 body 标签内容的开头或结尾。 @onefootswill 如果您有多个表单,您应该只生成一次令牌。它应该服务于那个request而不是一个单独的表单。【参考方案2】:

将 __RequestVerificationToken 添加到 FormData

 var formData = new FormData();
    formData.append("__RequestVerificationToken", token);
    formData.append("UserName", $scope.kullaniciAdi);
    formData.append("Password", $scope.sifre);

    $http(
        method: 'POST',
        url: '/Login/Login',
        data: formData,
        transformRequest: angular.identity, 
        headers:  'Content-Type': undefined 

    ).then(function successCallback(response) 



    , function errorCallback(response) 

    );

【讨论】:

【参考方案3】:

添加到 ASP.NET MVC 视图

<Form ng-submit="SubmitForm(FormDataObject)">
        @Html.AntiForgeryToken()
        .....
        ...
        .
</Form>

然后在 AngularJs 控制器上

angular.module('myApp', []).controller('myController', function ($scope, $http, $httpParamSerializerJQLike) 

        $scope.antiForgeryToken = angular.element('input[name="__RequestVerificationToken"]').attr('value');

        $scope.SubmitForm = function (formData) 
            var dataRequest = 
                __RequestVerificationToken: $scope.antiForgeryToken,
                formData: angular.toJson(formData)
            ;

            $http.post("/url/...", $httpParamSerializerJQLike(dataRequest),  headers:  'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'  ).then(function (response) 
                $scope.result = JSON.parse(response.data);
            );
        
    );

为什么要$httpParamSerializerJQLike(dataRequest)?因为没有那个 AngularJs 将数据序列化为:

__RequestVerificationToken: blablabla, formData: blablabla

并且 Asp.NET MVC 控制器抛出所需的防伪表单字段“__RequestVerificationToken”不存在错误。

但是如果你序列化请求数据 $httpParamSerializerJQLike(dataRequest),AngularJs 序列化为:

__RequestVerificationToken: blablabla
formData: blablabla

并且 Asp.NET MVC 控制器可以识别 Token 而不会出现任何错误。

【讨论】:

以上是关于AngularJS Web Api AntiForgeryToken CSRF的主要内容,如果未能解决你的问题,请参考以下文章

[angularjs] MVC + Web API + AngularJs 搭建简单的 CURD 框架

AngularJS Web Api AntiForgeryToken CSRF

使用AngularJS查询Web API

使用 web api 和 angularjs 下载 excel 文件

[Angularjs]asp.net mvc+angularjs+web api单页应用之CRUD操作

[Angularjs]asp.net mvc+angularjs+web api单页应用