Django:跨多个域维护会话

时间:2019-06-19 03:56:13

标签: python django django-authentication django-sessions

我有两个/多个域,分别是 foo.com bar.com ,并且它们都有相同的后端,这意味着两个域都将即将到来的域重定向对托管在其他地方的同一“网络实例” 的请求。


当前行为

如果用户登录 foo.com ,则他/她还需要登录 bar.com 才能访问任何端点/网址,例如 bar.com/some/url/end-point/


如果我的域名具有 通用模式 ,则SESSION_COOKIE_DOMAIN可能会做些事情。不幸的是,我没有。

问题
如何在多个域中维护用户会话?

3 个答案:

答案 0 :(得分:2)

从安全角度看,这本身就是一种风险,通过任何解决方法,一个域都可以从另一个域读取cookie。因此,出于明显的原因,这无法正常进行。

现在,在大多数情况下,您唯一想共享的是令牌或会话ID。因此,您可以通过不同的方式解决此问题

身份验证的重定向

让我们假设您的令牌是使用example.com/auth生成的。该网址可以返回cookie中的令牌以及json响应。然后,您还可以使该网址返回301到example.org/preauth?token=XXX。然后,该网址将使用令牌设置Cookie

因此,基本上,在这种情况下,您可以在服务器端本身处理整个方法

使用像素标签

在这种情况下,您想要做的是拥有一个像素标签网址。通过在example.com/auth

上进行身份验证收到身份验证令牌后

您将使用javascript在页面上动态添加一个图像源标签到您的其他域

<img src='http://example.org/cookiepixel?token=yyy' /> 

这将返回将在example.org中而不是example.com中设置的cookie

在这种方法中,您依赖客户端代码来确保进行跨域身份验证。

答案 1 :(得分:0)

我认为您不能跨完全不同的域进行单点登录。但是也许您可以使用OAuth authentication,两个域都指向同一个OAuth提供程序?然后实施一个OAuth提供程序,为任一域生成相同的访问令牌。我不知道可能要付出多少努力。

https://django-oauth-toolkit.readthedocs.io/en/latest/

答案 2 :(得分:0)

这是一个有趣的问题。应该有很多方法可以做到,我想到的第一件事就是使用iframe。下面的示例已通过Django 2.2进行了测试。

在您的settings.py中,将sessionid暴露给javascript。

SESSION_COOKIE_HTTPONLY = False

在您看来,请确保放上xfram_options_exempt,否则django将不允许它从另一个域“内嵌”,这里我使用模板视图,因此我将装饰器放在{{1}中}。

urls.py

from django.views.decorators.clickjacking import xframe_options_exempt urlpatterns = [ path( 'other_domain/', xframe_options_exempt(TemplateView.as_view(template_name='examplesite/otherdomain.html')), name='other_domain', ) # ... ] 是所有其他域的列表(不包括用户当前使用的域),在模板中,将其公开在domains标签中。

<head>

这将变成这样:

<head>
    {{ domains|json_script:"domains" }}
    {{ other_domain_path|json_script:"other-domain-path"}}
</head>

然后在您的JavaScript中:

<script id="domains" type="application/json">["c222dbef.ngrok.io"] </script>
<script id="other-domain-path" type="application/json">"/other_domain/"</script>

以上代码的想法是为彼此的域创建iframe,这些iframe的src指向名为{other_domain“的(function() { function getCookie(cname) { //copied from w3schools var name = cname + "="; var decodedCookie = decodeURIComponent(document.cookie); var ca = decodedCookie.split(";"); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == " ") { c = c.substring(1); } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } return ""; } function postSessionID(id) { var domains = JSON.parse(document.getElementById("domains").textContent); var path = JSON.parse(document.getElementById("other-domain-path").textContent); domains.forEach(function(domain) { var src = "https://" + domain + path; var iframeEl = document.createElement("iframe"); iframeEl.setAttribute("class", "invisible"); iframeEl.setAttribute("src", src); (function(id) { // this is an async call in a loop, create a closure here to protect "id" iframeEl.addEventListener("load", function() { this.contentWindow.postMessage(id, this.getAttribute("src")); }); })(id); document.body.appendChild(iframeEl); }); } function main() { var sessionID = getCookie("sessionid"); if (!sessionID) { return; } postSessionID(sessionID); } main(); })(); 。加载iframe后,我们使用view将会话ID发送给它们。

postMessage中:

examplesite/otherdomain.html

在您的脚本中:

<head>
    {{ domains|json_script:"domains" }}
    {# we also need to expose all other domains #}
</head>

现在您的用户可以登录和注销您的任何域,并且他们在所有域中都具有相同的会话。

我将完整的示例发布在我的github中:https://github.com/rabbit-aaron/django-multisite-sign-in

按照(function() { function setCookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; } var domains = JSON.parse(document.getElementById("domains").textContent); var trustedSources = domains.map(function(domain) { return "https://" + domain; }); window.addEventListener("message", function(e) { if (!e.origin in trustedSources) { return; // this prevents setting session id from other source } var sessionID = e.data; // you can probably get your cookie expiry from your django view, so all of your cookie expires at the same time setCookie("sessionid", sessionID, 365); }, false); })(); 进行设置。