【问题标题】:Django: maintain sessions across multiple domainsDjango:跨多个域维护会话
【发布时间】:2023-03-14 10:02:01
【问题描述】:

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


当前行为

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


如果我的域具有通用模式SESSION_COOKIE_DOMAIN 可能会有所作为。不幸的是,我没有。

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

【问题讨论】:

标签: python django django-authentication django-sessions


【解决方案1】:

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

现在,在大多数情况下,您唯一想分享的就是令牌或会话 ID。所以你可以用不同的方式解决这个问题

身份验证重定向

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

所以基本上,在这种情况下,您可以在服务器端自己处理整个方法

使用像素标签

在这种情况下,您要做的是拥有一个像素标签网址。一旦您通过在example.com/auth 上执行身份验证收到了身份验证令牌

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

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

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

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

【讨论】:

  • 如果您使用这两种方法,请务必检查“Referer”标头。否则,任何人都可以使用该图像标签设置您的用户会话 ID。例如,我是您网站的用户,然后我登录,获取我的会话 ID,然后我在我的网站中包含一个图像标签,任何访问我网站的人都会以我的身份登录。
  • 确实如此。我还将使用 JWT 的时间到期时间为 5 分钟左右,并且 cookie 会话也将与此令牌不同。具有更长的有效期。这将确保即使图像标签被重复使用它也不会长时间有效
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    这是一个有趣的问题。应该有很多方法,我首先想到的是使用iframe。下面的例子是用Django 2.2测试的。

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

    SESSION_COOKIE_HTTPONLY = False
    

    在你看来,一定要把xframe_options_exempt打上,否则django不会允许它被另一个域“iframed”,这里我使用模板视图,所以我把装饰器放在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 是所有其他域的列表(不包括您的用户现在所在的域),在您的模板中,在&lt;head&gt; 标记中公开它们。

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

    这将变成这样:

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

    然后在你的javascript中:

    (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,iframe 的 src 指向我们名为“other_domain”的view。加载 iframe 后,我们使用 postMessage 将会话 ID 发送给它们。

    examplesite/otherdomain.html:

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

    在您的脚本中:

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

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

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

    关注readme.md进行设置。

    【讨论】:

      猜你喜欢
      • 2015-11-22
      • 2011-02-09
      • 2012-03-08
      • 2013-10-03
      • 2019-08-16
      • 2013-12-17
      • 1970-01-01
      • 2010-09-19
      • 1970-01-01
      相关资源
      最近更新 更多