为什么使用 Bluemix Single Sign On 服务的应用程序 return 在水平扩展时出现 404?

Why does an application using the Bluemix Single Sign On service return a 404 when it is horizontally scaled?

我有一个简单的 Node.js 应用程序,它使用 express 4.12 和 express-session 1.11.2,使用 Bluemix 的单点登录 (SSO) 服务。当只有一个应用程序实例时它工作正常,但是当应用程序手动或自动缩放到两个实例时,它 returns 在用户提供其凭据后出现 404。

我使用 cf logs 检查应用程序的日志,在这两种情况下,应用程序回调都在身份验证后按预期调用:

工作(一个应用程序实例):

2015-07-02T10:11:38.45-0700 [RTR]     OUT ssolab-20150601tor.mybluemix.net - [02/07/2015:17:11:38 +0000] "GET /auth/sso/callback?scope=openid&code=JaoWziZSyRNNwrBDECRiWBSNHSl0CF HTTP/1.1" 302 0 68 "https://bestssoever-2zgs9sg44w-cge7.iam.ibmcloud.com/idaas/mtfim/sps/default/oidc/consent" "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" 75.126.70.43:34370 x_forwarded_for:"-" vcap_request_id:ec975656-70ae-44d8-4071-5b2d3b92e041 response_time:0.185715429 app_id:8b9f5b57-fbbd-4387-9cda-3737ad1f02ad 

不工作(两个应用程序实例):

2015-07-02T10:21:38.64-0700 [RTR]     OUT ssolab-20150601tor.mybluemix.net - [02/07/2015:17:21:38 +0000] "GET /auth/sso/callback?scope=openid&code=wR2FhOFxZnowIIggOqyytf9DzNmwSq HTTP/1.1" 404 0 83 "https://bestssoever-2zgs9sg44w-cge7.iam.ibmcloud.com/idaas/mtfim/sps/default/oidc/consent" "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" 75.126.70.43:39545 x_forwarded_for:"-" vcap_request_id:f1b75539-b02e-4217-4e3a-b3f6f1fc1c67 response_time:0.259509247 app_id:8b9f5b57-fbbd-4387-9cda-3737ad1f02ad 

我希望回调的 GET 请求中有不同的代码参数,所以没关系。

为了获得更多的调试信息,我在将用户定向到SSO服务进行身份验证之前添加了一个console.log语句,即运行。在回调中处理时,passport openid 连接节点模块 (strategy.js) 也会将输出记录到控制台。当一切正常工作时,输出如下所示:

2015-07-06T16:22:40.99-0700 [RTR]     OUT ssolab-20150601tor.mybluemix.net - [06/07/2015:23:22:40 +0000] "GET /hello HTTP/1.1" 302 0 68 "https://ssolab-20150601tor.mybluemix.net/" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)" 75.126.70.46:15290 x_forwarded_for:"-" vcap_request_id:fe3c942d-7ae2-4500-6274-b1ecf95f24e5 response_time:0.009553663 app_id:8b9f5b57-fbbd-4387-9cda-3737ad1f02ad
2015-07-06T16:22:41.01-0700 [App/0]   OUT Request by unauthenticated user

其次是:

2015-07-06T16:22:54.67-0700 [RTR]     OUT ssolab-20150601tor.mybluemix.net - [06/07/2015:23:22:54 +0000] "GET /auth/sso/callback?scope=openid&code=QkXlFtOEpBkNBJdo4tSvQA6qUlw7Q7 HTTP/1.1" 302 0 68 "https://bestssoever-2zgs9sg44w-cge7.iam.ibmcloud.com/idaas/mtfim/sps/default/oidc/consent" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)" 75.126.70.46:29117 x_forwarded_for:"-" vcap_request_id:ca85eb7a-8b54-4bc1-43a7-2cff279de499 response_time:0.223288172 app_id:8b9f5b57-fbbd-4387-9cda-3737ad1f02ad
2015-07-06T16:22:54.68-0700 [App/0]   OUT TOKEN

对于两个应用程序实例,初始应用程序流量似乎将流向一个实例:

2015-07-06T16:37:17.68-0700 [RTR]     OUT ssolab-20150601tor.mybluemix.net - [06/07/2015:23:37:17 +0000] "GET /hello HTTP/1.1" 302 0 68 "https://ssolab-20150601tor.mybluemix.net/" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)" 75.126.70.42:24841 x_forwarded_for:"-" vcap_request_id:491b5a52-653d-4fdc-6364-f604b3da395b response_time:0.008679282 app_id:8b9f5b57-fbbd-4387-9cda-3737ad1f02ad
2015-07-06T16:37:17.68-0700 [App/0]   OUT Request by unauthenticated user

而回调调用似乎由另一个实例处理:

2015-07-06T16:37:51.01-0700 [RTR]     OUT ssolab-20150601tor.mybluemix.net - [06/07/2015:23:37:50 +0000] "GET /auth/sso/callback?scope=openid&code=ylZKV3IPWMZgo2MNvwt9JxJzjU2Lsj HTTP/1.1" 404 0 83 "https://bestssoever-2zgs9sg44w-cge7.iam.ibmcloud.com/idaas/mtfim/sps/default/oidc/consent" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)" 75.126.70.43:48635 x_forwarded_for:"-" vcap_request_id:44b7893c-b185-4651-5384-7957a598bc20 response_time:0.273983071 app_id:8b9f5b57-fbbd-4387-9cda-3737ad1f02ad
2015-07-06T16:37:51.00-0700 [App/1]   OUT TOKEN

404 的原因是 Passport with Express 使用的 express-session 中间件有一个内存中的默认存储,并且不会扩展到单个进程(请参阅警告:https://github.com/expressjs/session

在失败的调试日志片段中,在将身份验证委托给 SSO 之前,Passport 使用 App/0 中的 express-session 管理的会话对象。但是,当调用回调时,请求由 App/1 处理,并且该实例中的 express-session 对原始会话不可见。

我通过使用 connect-redis 和用于 Bluemix 的 Redis 云服务为 express-session 实现替代(和持久)会话存储来测试这个假设。

配置 express-session 以将会话持久保存到该商店后,当多个应用程序实例处于活动状态时,404 错误消失了。