使用 Express 和 Node,如何跨子域/主机头维护会话
Posted
技术标签:
【中文标题】使用 Express 和 Node,如何跨子域/主机头维护会话【英文标题】:Using Express and Node, how to maintain a Session across subdomains/hostheaders 【发布时间】:2012-02-22 17:24:10 【问题描述】:我有一个单节点服务器,它响应请求并根据主机标头重定向用户。用法是静态/主站点位于 www,每个用户都有自己的子域(即 www.example.com 和 site.example.com)。路由是按照site.js。
当用户未登录时,他们将被重定向到登录。
我发现当用户被重定向到他们的子域时会话没有被维护。我想这是意料之中的,但我想知道是否有办法在两个子域之间保持相同的会话。
我希望如果他们登录并返回 www.example.com,他们会看到一个不同的视图,其中包括一个注销链接/他们的仪表板等。我目前的解决方法是,我在想,是只需在他们的子域上创建会话,如果他们确实返回 www,就好像他们没有登录一样。
之前有人处理过这个问题,或者有关于如何以这种方式处理会话的答案吗?
我认为问题可能出在 users.js 中,我重定向到“http://site.example.com”,因为它不是相对路径...
这里是相关代码(用户查找是使用 MongoDB 完成的,我将其省略了,因为它工作正常 - 调用此服务的行是 users.authenticate)...
server.js:
app.configure ->
app.set "views", "#__dirname/views"
app.set "view engine", "jade"
app.use express.bodyParser()
app.use express.methodOverride()
app.use express.cookieParser()
app.use express.session
key: "KEY",
secret: "SECRET",
store: new MemoryStore(),
cookie:
domain: 'example.com',
maxAge : 1000*60*60*24*30*12
app.use express.static "#__dirname/public"
app.use express.logger "short"
app.use express.favicon "#__dirname/public/img/favicon.ico"
app.use app.router
site.js:
module.exports = (app) ->
app.get '/', (req, res) ->
console.log "/ hit with #req.url from #req.headers.host"
domains = req.headers.host.split "."
org = if domains then domains[0] else "www"
if org == "www"
res.render "index", layout: null
else
if req.session.user
console.log "session established"
res.render "app", layout: null
else
console.log "no session"
res.redirect "http://www.example.com/accounts/login"
users.js:
users = require('../services/users')
module.exports = (app) ->
app.get "/accounts/login", (req, res) ->
res.render "login", layout: null, locals: returnUrl: req.query.returnUrl
app.post "/accounts", (req, res) ->
users.authenticate app, req.body.login, req.body.password, (user) ->
if user
req.session.user = user
res.redirect "http://#user.orgName.example.com"
else
res.render "login", layout: null, locals: returnUrl: req.body.url
app.get "/accounts/logout", (req, res) ->
console.log "deleting user from session"
delete req.session.user
res.redirect "http://www.example.com
为了在 OSX 上进行本地测试,我已将 www.example.com 和 site.example.com 添加到我的 hosts 文件中,以便在本地处理 DNS 查找。
【问题讨论】:
【参考方案1】:您是否确保将 cookie 设置为***域,以便所有子域都可以读取?那么这只是一个问题,或者像往常一样将您的会话数据保存在内存、数据库中。我没有启动并运行我的开发机器,但它会在你的 app.configure() 中像这样。
app.use(express.cookieParser());
app.use(express.session(
key: 'A_SESSION_KEY',
secret: 'SOMETHING_REALLY_HARD_TO_GUESS',
store: new express.session.MemoryStore,
cookie:
path : '/',
domain : 'yourdomain.com',
httpOnly : true,
maxAge : 1000*60*60*24*30*12 //one year(ish)
));
【讨论】:
我已经添加了您的建议,但没有任何区别。我尝试了“example.com”、“*.example.com”和“.example.com”。我在原始问题中添加了更多代码,以更好地详细说明该过程。我认为这可能是重定向的问题... @mattgi 更改代码后尝试删除浏览器中的所有 cookie。这个答案对我来说似乎很好。 @InspiredJW 已经做到了。也许是因为我在本地运行?当我设置 session.user = user 然后将会话注销到控制台时,我可以在其中看到它,但是当我重定向用户的那一刻消失了..它实际上可能与我尝试的子域“问题”无关解决,因为我的会话似乎根本无法跨任何路由(即使在同一个主机头上)。 @mattgi 你确认会话数据被保存在新的 MemoryStore() 中了吗?我不确定您的 MemoryStore() 对象可用于监控什么,但我会尝试将其切换到 Redis 或 Memcache 存储,这样如果您的存储不提供,我可以看到正在添加的键。【参考方案2】:首先要允许浏览器发出跨域请求,您需要在服务器端设置标头。此解决方案适用于正常请求以及 AJAX。 在您的快速配置功能中:
Express 4.0:
var express = require('express');
var session = require('express-session');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
app.use(session(
secret: 'yoursecret',
cookie:
path: '/',
domain: 'yourdomain.com',
maxAge: 1000 * 60 * 24 // 24 hours
));
app.use(function(req, res, next)
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
next();
);
如果不需要跨域 cookie 交换会话,Access-Control-Allow-Origin 可以设置为“*”。 要让 cookie 和会话跨域共享,您需要将特定的 Access-Control-Allow-Origin 设置为发出请求的实际域,这就是 req.headers.origin - 非常适合的原因。
使用域它不会在本地主机上正常工作 - 所以请确保在开发环境中禁用它,并在生产环境中启用它。它将启用跨***域和子域的共享 cookie。
这还不是全部。 它自己的浏览器不会通过跨域请求发送 cookie,这必须强制执行。 在 jQuery 中,您可以在 $.ajax() 请求中添加额外的参数:
xhrFields: withCredentials: true
对于非 jQuery,只需使用 XHR 构造函数并设置此参数即可:
xhr.withCredentials = true;
您已经准备好使用共享会话进行跨域了。
【讨论】:
用户$.ajaxSetup
将 withCredentials: true
添加到每个请求中
@Maksims "使用域在本地主机上无法正常工作" - 为什么它不能在本地主机上运行?我有: api.localhost:3000 和 localhost:3000 - 不工作? :\
由于浏览器的安全性,如果当前域与其定义的域不匹配,则不会发送cookie。对于开发和阶段环境,您可以注释掉域属性,或将其设置为“localhost”。
哎呀。我怎么会错过那部分。我的错。
Fetch API 怎么做?当我设置credentials: 'include'
时,它似乎没有设置任何cookie。有什么想法吗?【参考方案3】:
注意:如果使用 Express 4 和新的 cookie-session 模块,代码如下所示
secret: <session_secret> ,
store: <session store> ,
domain: '.domain.com',
这让我有点吃惊,但是 API 已经改变了。
【讨论】:
以上是关于使用 Express 和 Node,如何跨子域/主机头维护会话的主要内容,如果未能解决你的问题,请参考以下文章
React/Express set-cookie 不能跨不同的子域工作
带有 vhost 和 greenlock-express 的 NodeJS 子域