如何在一个浏览器中处理一个后端 CSRF 问题的两个网站?

Posted

技术标签:

【中文标题】如何在一个浏览器中处理一个后端 CSRF 问题的两个网站?【英文标题】:How to deal with two websites with one backend CSRF issue in one browser? 【发布时间】:2018-06-24 17:21:04 【问题描述】:

如何在一个浏览器中处理独立主页和管理网站的CSRF问题?

我们为它编写了一个主页网站前端和一个管理网站前端(有两个前端),在前端使用 javascript。 因为这两个站点都很大,所以我们将它们作为虚拟主机放在 nginx 中。它们是两个站点。我使用 Python (Django) 编写 一个 后端,两个站点都调用一个后端。

首页使用Nginx的default.conf,管理网站使用vhosts/admin.conf

这是测试域:

http://www.ajw123.xyz 作为主网站。http://admin.ajw123.xyz 作为管理网站。

我的麻烦是当我在浏览器中使用主网站上的帐户登录时:

然后我用账号登录admin网站(或其他账号),抛出CSRF Token Error:

你看csrftoken都是:

csrftoken=L5bRGEXDvW9dJaXsanLlMTOrxxGpxJCw6vji1zQtjzYrskOq0FBjQtfkhvFKFDmj; 

在预览中:

CSRF 失败:CSRF 令牌丢失或不正确。

其余API我使用Django-Rest-Framework,我们的前端同事写了两个站点(一个是普通用户的前端和后端站点,另一个是管理员的后端站点),并且都在使用我的rest API。

【问题讨论】:

在每一个上使用不同的cookie名称 @charlietfl 怎么办?你的意思是在 Nginx 中? 在管理 csrf 的任何 Django 模块中。我不是 django 开发人员,但在其他环境中做过此操作 @charlietfl 还是谢谢你。 这是您在问题中省略的非常重要的信息。所以基本上你的 Django 项目是一个 REST API。前端(家庭和管理员)是使用 API 的客户端。正确的?你读过this page of the documentation吗? 【参考方案1】:

在我看来,问题是XY problem 的一个例子。在下面的文字中,我将回到我的主张并解释它。

OP 使用 Django REST Framework 编写了一个 REST API。忽略这些信息最初会导致非常低的关注度。包含这些信息后,事情变得更加清晰。

让我们首先回顾一些有关 REST API 的基础知识。 REST API 与语言无关。它不关心客户端是用哪种语言编写的,客户端也不关心 API 是用哪种语言编写的。可以通过不同方式访问(使用)REST API:从命令行使用curl;来自用任何编程语言编写的脚本;来自使用(很可能)JavaScript(或 JavaScript 框架)的浏览器。

由于有两个网站使用 API,OP 希望为他们提供对 API 的访问权限。出现的障碍是 CSRF(跨站请求伪造)。 Django 使用 CSRF 令牌实现了 CSRF 保护。这意味着我们保护我们的网站免受来自其他网站的请求,通常可以将表格发布到我们的网站。

在实际情况下,客户端是托管在不同域上的两个不同网站,因此来自它们的请求来自不同的站点。 OP 真正想知道的是:“如何授予或限制对使用我的 API 的其他网站的访问?” 而不是:“如何处理 CSRF 问题?”

Django REST Framwork 的官方文档有专门讨论这个问题的页面:Working with AJAX, CSRF & CORS

这是 CORS 部分:

跨域资源共享是一种允许客户端与托管在不同域中的 API 交互的机制。 CORS 的工作原理是要求服务器包含一组特定的标头,这些标头允许浏览器确定是否以及何时允许跨域请求。

在 REST 框架中处理 CORS 的最佳方法是在中间件中添加所需的响应头。这可确保透明地支持 CORS,而无需更改视图中的任何行为。

Otto Yiu 维护着 django-cors-headers 包,已知它可以与 REST 框架 API 一起正常工作。

我会强调第一句话:

跨域资源共享是一种允许客户端与托管在不同域中的 API 交互的机制。

正是如此。 OP 希望允许客户端与其托管在不同域上的 API 进行交互。 最后一句推荐使用django-cors-headers,就是解决问题的办法。 可以在其文档中找到有关使用该应用程序的所有详细信息。

【讨论】:

【参考方案2】:

CSRF 令牌本质上是一个可检索的 cookie。 默认情况下,对于每个 django 应用,此 cookie 的名称为 csrftoken

您需要使用CSRF_COOKIE_NAME 设置更改至少一个cookie 的名称(在您的settings.py 中)。

然后您的同事可以通过以下 AJAX 调用检索该 cookie:

// using jQuery
function getCookie(name) 
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') 
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) 
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) 
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            
        
    
    return cookieValue;

var csrftoken = getCookie('name_of_your_token');

对于更复杂的用途,请查看文档:https://docs.djangoproject.com/en/2.0/ref/csrf/


类似案例:Multiple Django sites on the same domain - CSRF fails

【讨论】:

你的意思是只是在前端更改cookie名称吗?我应该在 DRF 后端进行配置吗? @aircraft 您需要配置 DRF 后端以向您的 csrf cookie 添加不同的名称。您的同事需要重构他们的网站以使用这些 cookie。 (您也可以使用链接文档中提供的内容对其进行测试) 我阅读了有关CSRF_COOKIE_NAME 的文档,但您应该注意我的情况是:在一个浏览器中。有两个前端站点,都使用一个 django 后端。如果我设置CSRF_COOKIE_NAME,是不是前端csrftoken的名字都改了? @aircraft 正如cezar 所说,您阅读过文档吗? django-rest-framework.org/topics/ajax-csrf-cors 我对您的回答表示敬意,因为它展示了如何处理多个站点的 CSRF 令牌。但是我认为这是一个 XY 问题,这是对 Y 的回答,并按照 OP 的要求发布了自己的答案。【参考方案3】:

感谢@cezar 和@JohnMoutafis,我阅读了CORS 的文档:

跨域资源共享是一种允许客户端与托管在不同域中的 API 交互的机制。 CORS 的工作原理是要求服务器包含一组特定的标头,这些标头允许浏览器确定是否以及何时允许跨域请求。

然后我安装了django-cors-headers,并使用它。

我设置了CORS_ORIGIN_WHITELIST:

CORS_ORIGIN_WHITELIST = (
    'http://10.10.10.102:8000', # normal user site
    'http://10.10.10.102:8080', # admin backend site
)

现在它工作正常。

再次感谢您。

【讨论】:

你去!你有答案和解决方案。 @cezar 答案是您和 JohnMoutafis 的贡献。你可以在那里发布你的答案然后我选择你的答案,因为我不想浪费赏金。

以上是关于如何在一个浏览器中处理一个后端 CSRF 问题的两个网站?的主要内容,如果未能解决你的问题,请参考以下文章

对于仅使用JSON的后端REST应用程序,CSRF是强制性的吗?

后端和前端分离时的Django CSRF

如何将 POST 数据中的 CSRF 令牌传递给 Django?

如何将 CSRF 令牌从 AngularJS 前端发送到 Spring REST 服务后端?

在 Spring Boot 和 Angular 之间处理 CORS 和 CSRF

如何解决Django中CSRF问题