在 CORS 调用后,Express Session 不会持续存在

Posted

技术标签:

【中文标题】在 CORS 调用后,Express Session 不会持续存在【英文标题】:Express Session not persisting after CORS calls 【发布时间】:2016-03-01 17:53:54 【问题描述】:

TL;DR:

无法跨执行的多个 API 调用成功保持会话 在 Backbone 应用程序和带有 Express 的 Node.js 服务器之间, Express-Session 和 Express-Cors。看起来会话已重新初始化/丢失 每次通话后。

加长版:

我有一个在localhost:3000 上运行的客户端 Backbone/React/Flux 应用程序在localhost:4242 上运行的 Node.js 服务器上执行以下调用:

Http 调用

发布http://localhost:4242/api/session

 RESPONSE HEADERS
 Content-Type application/json; charset=utf-8
 Set-Cookie connect.sid=s%3AFeNYY5GQGvkyRvOym7DhysprePaQr7xP.BrxOPP56k9pDpxQPvwjDFaxkEYoHU%2FAEtNUIXGltqjI; Domain=http://localhost:3000; Path=/
 Vary Origin
 X-Powered-By Express
 access-control-allow-credentials   true
 access-control-allow-orign http://localhost:3000
 [...]

 REQUEST HEADERS
 Accept application/json, text/javascript, */*; q=0.01
 Content-Type application/json; charset=utf-8
 Cookie connect.sid=s%3AjP4iADZvRnDHbJcCE8H81Zy6TIehj9BJ.eDOTw44GcHqq8i2dslBGd43tXMQ22ENl31fRizdC8iA
 Host localhost:4242
 Origin http://localhost:3000
 Referer http://localhost:3000/login
 [...]

获取http://localhost:4242/api/users

 RESPONSE HEADERS
 Content-Type application/json; charset=utf-8
 Set-Cookie connect.sid=s%3ARxf91_vLMBqzB6xN-0QFIIk_-SyBP9_8.F1Mr%2BVSkYNJ6MqnzO%2BsxxfwXRinIX6th80SoukG1QBM;Domain=http://localhost:3000; Path=/
 Vary Origin
 X-Powered-By Express
 access-control-allow-credentials   true
 access-control-allow-orign http://localhost:3000
 [...]

 REQUEST HEADERS
 Accept application/json, text/javascript, */*; q=0.01
 Content-Type application/json; charset=utf-8
 Cookie connect.sid=s%3AjP4iADZvRnDHbJcCE8H81Zy6TIehj9BJ.eDOTw44GcHqq8i2dslBGd43tXMQ22ENl31fRizdC8iA
 Host localhost:4242
 Origin http://localhost:3000
 Referer http://localhost:3000/login
 [...]

基本上,第一次调用POST /api/session 是登录用户并尝试在会话中存储 API 令牌。 第二次调用GET /api/users 在第一次调用成功后立即触发,并检索用户信息。

主干方法

这是我在 Session 模型上用于登录的 Backbone 方法:

  login: (options) ->
    @set user: options.user, password: options.password
    @save ['user', 'password'],
      success: (data) =>
        @set(authenticated: true, accessToken: data.accessToken, password: null)
        options.success(data) # trigger the second call here
      error: (error) =>
        options.error(error)

以及在我的 UserStore 中对 /api/users 的调用

users: (options) ->
  @users.fetch
    success: (users) =>
      @users = users
      options.success(users)

使用这些不同的选项(我在 Backbone.Collection/Backbone.Model 中覆盖了 Backbone.sync):

class UsersCollection extends Backbone.Collection

  url: '/api/users'
  model: UserModel

  sync: (method, model, options) ->
    options ?= 
    options.url ?= @url
    options.dataType ?= 'json'
    options.contentType ?= "application/json; charset=utf-8"
    options.crossDomain ?= true
    options.xhrFields ?= "withCredentials": true
    super(method, model, options)

(简化版:Models 和 Collection 相同,使用 BaseCollection 和 BaseModel,我在其中覆盖了 sync() 方法)。

所以Backbone.sync(method, model, options) 中的Console.log(options) 正在返回:

"url":"http://localhost:4242/api/session","dataType":"json","contentType":"application/json; charset=utf-8","crossDomain":true,"validate":true,"parse":true,"xhrFields":"withCredentials":true 

Node.js 设置和方法

这是我的 Node.js 路由器设置 Express:

BodyParser = require 'body-parser'
Session = require 'express-session'
Cors = require 'cors'

class Router

  constructor: (express) ->
    @express = express
    @express.use BodyParser.json()
    @express.use Cors(@corsConfig())
    @express.use Session(@sessionConfig())
    # Express routes are set here
    # @express.post '/api/session', (request, response) => [...]
    # @express.get '/api/users', (request, response) => [...]

  corsConfig: ->
    origin: 'http://localhost:3000'
    credentials: true

  sessionConfig: ->
    secret: 'whatever'
    cookie:
      secure: false
      httpOnly: false
      domain: 'http://localhost:3000'

这是我的 Node.js 方法处理 POST /api/session

  login: (request, response) ->
    session = request.session
    console.log JSON.stringify(session)
    console.log request.sessionID
    console.log '---------------------------------------------'
    if session.accessToken
      console.log 'session with token!'
      response.json accessToken: session.accessToken
    else
      console.log 'performing credentialAuthentication'
      user = request.body.user
      password = request.body.password
      @whatever.authentication
        user: user
        password: password
        success: (accessToken) ->
          request.session.accessToken = accessToken
          console.log JSON.stringify(session)
          console.log request.sessionID
          console.log '---------------------------------------------!!!'
          response.json accessToken: accessToken
          # also tried with response.send()

还有一个处理GET /api/users

  @express.get '/api/users', (request, response) =>
    console.log JSON.stringify(request.session)
    console.log request.sessionID
    console.log '---------------------------------------------'
    [...]

Node.js 日志

这是日志:

  express:router dispatching OPTIONS /api/session
  express:router dispatching POST /api/session
"cookie":"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":false,"path":"/"
zse18d2zrNRdEXPjFHF0gm3NkONb-_5V
---------------------------------------------
performing credentialAuthentication
"cookie":"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":false,"path":"/",
"accessToken":"ebab5010f9ece5ea984e4b73f9a46ef3"
zse18d2zrNRdEXPjFHF0gm3NkONb-_5V
---------------------------------------------!!!
  express:router dispatching GET /api/users
"cookie":"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":false,"path":"/"
g8YXQEpt_rnWSGdh1nCKMndiI8Lt2UDq
---------------------------------------------

您可以看到 CORS 请求正常执行,我正确获取了我的令牌,然后尝试将其存储在会话中。

但是在第二次调用中,会话没有持久化,我无法访问我在第一次调用中实际设置的变量 (accessToken)。

查看日志和两次调用的 HTTP 标头,看起来会话每次都重新初始化,因为会话 ID 和每个请求都在更改 - 并且每次都发送一个 Set-Request 标头(其中据我所知,不应该是这种情况)。

我怀疑这种行为是由 CORS 级别的一些不连贯或缺失的配置引起的,或者是由于为 Sessions 设置了 pathExpress.use(path, middleware)Cookie(path: '/'))。然而,尽管多次尝试使用不同的配置、设置和标题,但我真的无法让它工作。

非常欢迎任何能够就这种行为以及我所缺少的东西向我提供启发的人 :) 谢谢!

PS:我向非 CoffeeScript 开发者道歉 ;)

【问题讨论】:

你怎么打电话给GET /api/users,有什么选择......? 顺便说一句,您是否在 POSTGET 处理程序之前添加会话中间件..?还是您在内存会话存储中使用默认值..?如果您碰巧在这些请求之间重新启动服务器,那么将创建新会话......我知道概率非常小,但如果它有帮助:v 好吧,我在路由器类中设置会话中间件,然后才设置快速路由。还尝试为每个快速路由设置 Session 中间件,但当然它不会改变任何东西:( Session 应该存储在内存中,当然我不希望服务器在重新启动时持久保存它们;执行了两个调用在同一个实例中,一个接一个,正如您在代码中看到的那样。 要让 cookie 在 CORS 上运行,浏览器必须支持第 3 方 cookie。现在很多浏览器都屏蔽了它。 我正在使用最新版本的 Firefox 和 Chrome 进行测试;两者都支持 3rd 方 cookie... 【参考方案1】:

这是答案(简单的一个): 我错误地配置了我的 Session 中间件; cookie.domain 选项导致了问题。 这是正确的配置:

sessionConfig: ->
  secret: 'whatever'
  cookie:
    secure: false
    httpOnly: false

express/session 中间件不需要/不存在此选项(不再存在?);不知道为什么我最初使用它,可能来自过时的参考资料(表达/cookie-parser 或 express/cors)。

【讨论】:

嗨,我正在从我的节点 js 应用程序到 Spring Security 应用程序进行 API 调用。我收到Could not verify the provided CSRF token because your session was not found。我该如何解决这个问题?我试图在标头中传递X-XSRF-TOKEN,但似乎我必须传递会话以及现有请求。有什么想法吗?

以上是关于在 CORS 调用后,Express Session 不会持续存在的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 Express.js 后端的 CORS 设置不起作用?

CORS“允许凭据”Nodejs/Express

在 Node.js Express.js 服务器中添加标头后,Chrome 中仍然出现 CORS 错误

Express CORS 子域

Graphql shield 要求在 express 服务器上启用 cors

使用 cors、express 和 google api 的 Node.js 服务器应用程序在 Azure 部署后无法正常工作