Angularjs 标头中的CSRF令牌与请求中的cookie不匹配

Angularjs 标头中的CSRF令牌与请求中的cookie不匹配,angularjs,cookies,http-headers,csrf,Angularjs,Cookies,Http Headers,Csrf,我正在实现一个无状态API,我的组织说我需要防止CSRF攻击 我在网上找到了这家伙的解决方案,并决定尝试实现一种仅客户端的方法: 以下是网站对无状态解决方案的建议(以防网站宕机): 客户端生成的CSRF-Token。让客户端在Cookie和自定义HTTP中生成并发送相同的唯一机密值 标题。考虑到网站只允许读/写Cookie 对于自己的域,只有真正的站点才能在这两个域中发送相同的值 标题。使用这种方法,服务器所要做的就是检查 这两个值是相等的,每个请求都是无状态的 不幸的是,它不起作用。我的头值从

我正在实现一个无状态API,我的组织说我需要防止CSRF攻击

我在网上找到了这家伙的解决方案,并决定尝试实现一种仅客户端的方法:

以下是网站对无状态解决方案的建议(以防网站宕机):

  • 客户端生成的CSRF-Token。让客户端在Cookie和自定义HTTP中生成并发送相同的唯一机密值 标题。考虑到网站只允许读/写Cookie 对于自己的域,只有真正的站点才能在这两个域中发送相同的值 标题。使用这种方法,服务器所要做的就是检查 这两个值是相等的,每个请求都是无状态的 不幸的是,它不起作用。我的头值从不匹配cookie值,在某些情况下,我的头似乎只是匹配cookie值后的一个请求

    这是我的角度代码:

     app.config(['$httpProvider', function ($httpProvider) {
         //fancy random token
         function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e16]+1e16).replace(/[01]/g,b)};
    
         $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
         $httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN';
    
         $httpProvider.interceptors.push(function () {
             return {
                 'request': function (config) {
                     document.cookie = 'CSRF-TOKEN=' + b();
                     return config
                 }
             };
         });
     }]);
    
    以下是正在发送的CSRF值的一些示例

     CSRF-TOKEN=d25cf03a985d575ad48a863eac91467666
     X-CSRF-TOKEN:fa1f165df8b27195a90f5e7841108f4e42
    
     CSRF-TOKEN=d25cf03a985d575ad48a863eac91467666
     X-CSRF-TOKEN:fa1f165df8b27195a90f5e7841108f4e42
    
     CSRF-TOKEN=9c8dd46ed06c250b707ac0cb80a08a23ac
     X-CSRF-TOKEN:d25cf03a985d575ad48a863eac91467666
    
     CSRF-TOKEN=eb407a0303c21173fe4d0ae03c97eaea6d
     X-CSRF-TOKEN:0cf066bf83e50b5c74cb932ab8a47c94e8
    
     CSRF-TOKEN=506355a940a2ac5b48f363712b34570d73
     X-CSRF-TOKEN:eb407a0303c21173fe4d0ae03c97eaea6d
    

    这是怎么回事?我觉得我在做那个家伙解决方案中的所有事情,但结果却很奇怪。

    我最终没有发送客户端在每个请求中创建的随机令牌。我无法让它工作

    下面是我解决问题的方法(有点):

    (1) 对于每个请求(包括第一个请求),我都会从API发回响应头中的一个cookie,其名称为“XSRF-TOKEN”和一个与之相关的随机值。这是AngularJS在使用其CSRF保护时默认寻找的名称

    (2) 在收到该令牌之后的请求中,AngularJS使用该令牌的值在名为“XSRF-token”的请求头中发送cookie,并在名为“X-XSRF-token”的头中发送该令牌的值

    因此,我的API正在处理随机XSRF令牌,而我的应用程序仍然是无状态的。我使用Web API,并使用全局过滤器来处理XSRF令牌的创建。下面是我的代码(用C#)。我不再在UI中使用任何代码处理此问题(因为似乎不需要):

    以下是我的HashService.HashText()代码:

    希望这有助于未来无状态应用程序的开发。不幸的是,发送回的令牌仅在cookie值和头值中与自身进行比较。这是我现在能够验证它的唯一方法(我觉得这很安全)。我可能会为XSRF保护创建一个全新的表,并使用它来验证令牌是否确实是用户应该使用的令牌。这是唯一一种让API保持无状态的方法

    在阅读了AngularJS的$http文档后,我偶然发现了这个解决方案,其中规定:

    跨站点请求伪造(XSRF)保护:XSRF是由 未经授权的站点可以获取用户的私有数据。有棱角的 提供一种机制来对抗XSRF。执行XHR请求时, $http服务从cookie中读取令牌(默认情况下,XSRF-token) 并将其设置为HTTP头(X-XSRF-TOKEN)。因为只有JavaScript 在您的域上运行的可以读取cookie,您的服务器可以 确保XHR来自在您的域上运行的JavaScript。这个 不会为跨域请求设置标头

    要利用这一点,服务器需要在 JavaScript可读会话cookie,称为第一个HTTP上的XSRF-TOKEN 获取请求。在后续的XHR请求中,服务器可以验证 cookie与X-XSRF-TOKEN HTTP头匹配,因此请确保 只有在您的域上运行的JavaScript才能发送请求。 令牌对于每个用户都必须是唯一的,并且必须可由 服务器(防止JavaScript生成自己的令牌)。我们 建议令牌是站点身份验证的摘要 为了增加安全性,饼干中加入了盐

    可以使用xsrfHeaderName和 $httpProvider.defaults的xsrfCookieName属性位于 配置时间,$http.defaults在运行时,或每请求配置 反对


    >考虑到一个网站只允许为其自己的域读/写Cookie,只有真正的网站才能在两个标题中发送相同的值。当多个网站共享同一个域时会发生什么?这会让你的网站免受其他共享网站的攻击吗?我想如果你确信你的网站将是这个领域中唯一的一个,这种方法可能会奏效。
    public class ValidateAntiForgeryToken : ActionFilterAttribute
    {
        private const string XsrfCookieName = "XSRF-TOKEN";
    
        private const string XsrfHeaderName = "X-XSRF-TOKEN";
    
        private const string CsrfTokenSalt = "RANDOM SALT";
    
    
        public override void OnActionExecuting(HttpActionContext filterContext)
        {
            string requestMethod = filterContext.Request.Method.Method;
    
            Boolean isValid = true;
    
            if (requestMethod != "GET")
            {
                var headerToken = filterContext.Request.Headers.Where(x => x.Key.Equals(XsrfHeaderName, StringComparison.OrdinalIgnoreCase))
                    .Select(x => x.Value).SelectMany(x => x).FirstOrDefault();
    
                var cookieToken = filterContext.Request.Headers.GetCookies().Select(x => x[XsrfCookieName]).FirstOrDefault();
    
                // check for missing cookie or header
                if (cookieToken == null || headerToken == null)
                {
                    isValid = false;
                }
    
                // ensure that the cookie matches the header
                if (isValid && !String.Equals(headerToken, cookieToken.Value, StringComparison.OrdinalIgnoreCase))
                {
                    isValid = false;
                }
    
                if (!isValid)
                {
                    filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
                    filterContext.Response.ReasonPhrase = "Unauthorized to make that request.";
                    return;
                }
            }
    
            base.OnActionExecuting(filterContext);
        }
    
    
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            string textToHash = RandomStringGeneration();
            string cookieText = HashService.HashText(textToHash, CsrfTokenSalt);
    
            var cookie = new CookieHeaderValue(XsrfCookieName, HttpUtility.UrlEncode(cookieText));
    
            /* don't use this flag if you're not using HTTPS */
            cookie.Secure = true;      
            cookie.HttpOnly = false; // javascript needs to be able to get this in order to pass it back in the headers in the next request
    
            /* if you have different environments on the same domain (which I did in one application using this code) make sure you set the path to be ApplicationPath of the request. Case sensitivity does matter in Chrome and IE, so be wary of that. */
            cookie.Path = "/";
    
            actionExecutedContext.Response.Headers.AddCookies(new[] { cookie });
    
            base.OnActionExecuted(actionExecutedContext);
        }
    }
    
    public class HashService
    {
        public static string HashText(string text, string salt)
        {
            SHA512Managed hashString = new SHA512Managed();
    
            byte[] textWithSaltBytes = Encoding.UTF8.GetBytes(string.Concat(text, salt));
            byte[] hashedBytes = hashString.ComputeHash(textWithSaltBytes);
    
            hashString.Clear();
    
            return Convert.ToBase64String(hashedBytes);
        }
    }