[英]Django: maintain sessions across multiple domains
我有兩個/多個域,例如foo.com
和bar.com
,它們都具有相同的后端,這意味着兩個域都將即將到來的請求重定向到托管在其他地方的同一個“Web 實例” 。
如果用戶登錄foo.com
,他/她還需要登錄bar.com
才能訪問任何端點/URL,例如bar.com/some/url/end-point/
。
如果我的域具有通用模式,則SESSION_COOKIE_DOMAIN
可能會做一些事情。 不幸的是,我沒有。
題
如何跨多個域維護用戶會話?
當您從安全角度來看時,這本身就是一種風險,任何解決方法的一個域都可以從另一個域讀取 cookie。 所以出於顯而易見的原因,這不能正常工作。
現在在大多數情況下,您唯一想要共享的是令牌或會話 ID。 所以你可以用不同的方式來解決這個問題
假設您的令牌是使用example.com/auth
生成的。 這個 url 可以返回 cookie 中的令牌以及 json 響應。 然后你也可以讓這個 url 返回一個 301 到example.org/preauth?token=XXX
。 然后,此 url 將使用令牌設置 cookie
所以基本上,在這種情況下,您可以在服務器端處理整個方法
在這種情況下,您想要做的是擁有一個像素標簽網址。 通過在example.com/auth
上執行 auth 收到 auth 令牌后
您將使用 javascript 在頁面上動態添加一個圖像源標簽到您的另一個域
<img src='http://example.org/cookiepixel?token=yyy' />
這將返回將在example.org
而不是example.com
設置的 cookie
在這種方法中,您依賴於客戶端代碼來確保跨域身份驗證發生。
我認為您無法跨完全不同的域進行單點登錄。 但也許您可以使用OAuth 身份驗證,兩個域都指向同一個 OAuth 提供者? 然后實現一個 OAuth 提供程序,為任一域生成相同的訪問令牌。 我不知道這可能需要多少努力。
這是個有趣的問題。 應該有很多方法可以做到,我想到的第一件事就是使用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
是所有其他域的列表(不包括您的用戶現在所在的域),在您的模板中,在<head>
標記中公開它們。
<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
進行設置。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.