存在 Express 会话问题的节点

Posted

技术标签:

【中文标题】存在 Express 会话问题的节点【英文标题】:Node with Express session issue 【发布时间】:2020-11-25 06:37:26 【问题描述】:

我使用以下代码有效,但是在几次成功调用(5-10)之后,我们有时会收到内部服务器错误:

req.session["oidc:accounts.rvm.com"] is undefined

我已经尝试了所有latest 开源版本。

Error: did not find expected authorization request details in session, req.session["oidc:accounts.rvm.com"] is undefined
at /opt/node_app/app/node_modules/openid-client/lib/passport_strategy.js:125:13
at OpenIDConnectStrategy.authenticate (/opt/node_app/app/node_modules/openid-client/lib/passport_strategy.js:173:5)
at attempt (/opt/node_app/app/node_modules/passport/lib/middleware/authenticate.js:366:16)
at authenticate (/opt/node_app/app/node_modules/passport/lib/middleware/authenticate.js:367:7)
at /opt/node_app/app/src/logon.js:92:7 *******
at Layer.handle [as handle_request] (/opt/node_app/app/node_modules/express/lib/router/layer.js:95:5)
at next (/opt/node_app/app/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/opt/node_app/app/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/opt/node_app/app/node_modules/express/lib/router/layer.js:95:5)
at /opt/node_app/app/node_modules/express/lib/router/index.js:281:22

我的堆栈代码是:

at /opt/node_app/app/src/logon.js:92:7

这里的代码结束了:

)(req, res, next);   // here is line 92 but not sure if it's related 

这是完整的代码(我传递了app,它只是一个快速服务器):

index.js

const express = require('express');
const logon = require('./logon');

const app = express();
const port = process.env.PORT || 4000;

logon(app)
  .then(() => 
    console.log('process started');
  );
app.use(express.json());

app.listen(port,
  () => console.log(`listening on port: $port`));

logon.js

const  Issuer, Strategy  = require('openid-client');
const cookieParser = require('cookie-parser');
const cookieSession = require('cookie-session');
const azpi = require('./azpi');
const bodyParser = require('body-parser');
const passport = require('passport');

module.exports = async (app) => 
  let oSrv;
  const durl = `$process.env.srvurl/.well-known/openid-configuration`;
  try 
    oSrv = await Issuer.discover(durl);
   catch (err) 
    console.log('error occured', err);
    return;
  

  app.get('/', prs(), passport.authenticate('oidc'));

  const oSrvCli = new oSrv.Client(
    client_id: process.env.ci,
    client_secret: process.env.cs,
    token_endpoint_auth_method: 'client_secret_basic',
  );

  passport.serializeUser((user, done) => 
    done(null, user);
  );
  passport.deserializeUser((obj, done) => 
    done(null, obj);
  );

  const cfg = 
    scope: 'openid',
    redirect_uri: process.env.ruri,
    response_type: 'code',
    response_mode: 'form_post',
  ;

  const prs = () => (req, res, next) => 
    passport.use(
      'oidc',
      new Strategy( oSrvCli , cfg , (tokenset, done) => 
        const claims = tokenset.claims();
        // first log
        console.log(`1. ------------User claims received------------);
        const user = 
          name: claims.name,
          id: claims.sub,
          id_token: tokenset.id_token,
        ;
        return done(null, user);
      ),
    );
    next();
  ;
  app.use(
    bodyParser.urlencoded(
      extended: false,
    ),
  );
  app.use(cookieParser('csec'));
  app.use(
    cookieSession(
      name: 'zta-auth',
      secret: 'csect',
    ),
  );

  app.use(passport.initialize());
  app.use(passport.session());

  app.get('/redirect', async (req, res, next) => 
    await passport.authenticate('oidc', async (err, user) => 
    // print second log
    console.log('2. ------------redirect Called!------------');
      if (err) 
        console.log(`Authentication failed: $err`);
        return next(err);
      
      if (!user) 
        return res.send('no identity');
      

      req.login(user, async (e) => 
        if (e) 
          console.log('not able to login', e);
          return next(e);
        
        try 
          const url = await azpi.GetUsers(user.id_token);
          // print last log
          console.log('3. ------------user process finished successfully----');
          return res.redirect(url);
          
         catch (er) 
          res.send(er.message);
        
      );
    )(req, res, next);   //here is the error
  );
;

有时当我调试时,我看到函数从 GetUsers 用尽,这是一个异步函数并在 )(req, res, next); 停止,可能是异步问题。

我们想在 prod 中使用这段代码,而不是之前的 Java 实现。

如果我可以对 oidc 使用其他技术,请告诉我。


更新

每个都应该是一个单独的调用并按以下顺序登录:

1. ------------User claims received------------
2. ------------redirect Called!------------
3. ------------user process finished successfully----

但是,当我收到 错误 时:

1. ------------User claims received------------
2. ------------redirect Called!------------
3. ------------user process finished successfully----

2. ------------redirect Called!------------
Authentication failed: Error: did not find expected authorization request details in session, req.session

所有成功的调用都有正确的日志顺序(1-3)。

当它失败时,第一次调用User claims received 不会发生,只会发生第二次和错误。

如果有其他方法可以实现这一点(其他库等),请告诉我。


我发现 this 库可能会有所帮助,因为它不使用护照(我想减少部门以查看问题出在哪里)。

当我尝试这样的事情时:

app.use(
    auth(
     issuerBaseURL: `$URL/.well-known/openid-configuration`,
     authorizationParams: 
    ...
     response_mode: 'form_post',
    

我收到此错误:issuer response_mode supporting only "query" or "fragment",但是当我使用相同的 issuerresponse_mode 运行上面的代码(在帖子的开头)时,一切正常,有什么想法吗?

【问题讨论】:

评论不用于扩展讨论;这个对话是moved to chat。 一个严重的问题:你在定义函数之前调用prs(),这会引发异常。 我也不知道护照,但是你会在每个请求的中间件中创建一个new Strategy 看起来很奇怪,尽管它似乎独立于req 和@ 987654344@。我会假设 passport.use(…) 应该只被调用一次。 @Bergi - 谢谢,prs() 我已经更改了它并得到了相同的结果,关于护照使用不确定如何更改我的代码,您能否提供一个示例作为答案我试试看? @James But still cause an exception 【参考方案1】:

问题似乎是一种竞争条件,如果您碰巧同时收到两个正在进行的请求,当一个请求完成时,它会在另一个有机会完成之前清除会话 cookie。对于它的价值,你不是唯一拥有这个 this problem 的人。

我认为这不是库本身的问题,我认为问题更多在于会话库。您可能想尝试将saveUninitialized / resave 选项设置为falseexpress-session 库,并检查您是否仍然看到相同的问题,例如

const session = require('express-session');
...
app.use(session(
  saveUninitialized: false,
  resave: false
);

这个库与您使用的cookie-session 的唯一区别是express-session 仅将会话ID 存储在cookie 中,数据存储在服务器端。如果您发现它有效,那么您可以考虑使用更多的生产级存储(默认设置是使用内存中的存储)。

FWIW - 你只需要配置一次策略,看看它的作用,如果这是问题的一部分,我会感到惊讶,但我会修复它以防万一

【讨论】:

谢谢,我会尝试更改会话并告诉您,您将如何更改代码以仅使用一种策略,您能否在我的上下文中给我一个示例? 我试过express-session,我得到了同样的错误:( @BenoOdr 所以我想知道....这将是一个多大的现实问题?大概只有当你有同一个客户端同时发起同一个请求时才会发生这种情况?具有讽刺意味的是,可能有一种方法可以避免这种情况 - 如果您保留每个请求的策略,那么您可以覆盖 sessionKey 使其在每个请求中都是唯一的,这样您就不会发生冲突。 如果您在我的上下文中提供示例如何做到这一点会很棒,我会尽快。谢谢 @Beno 只保留您在logon.js 中的代码,但是在创建策略时,每次都将sessionKey 选项作为唯一的东西传递,例如uuid。或者更简单,只传入一个随机数(只是为了测试)。【参考方案2】:

一种想法是,如果您决定启用会话,那么您需要在 passport.session() 之前使用 express.session() 以确保以正确的顺序恢复用户的登录会话。

看到这个article

【讨论】:

谢谢,我试过了:` app.use(expressSesssion( secret: 'kbrsession', resave: false, saveUninitialized: true, ), ); app.use(passport.initialize()); app.use(passport.session()); ` 但仍然是相同的错误: (,我想使用这个不需要护照的库 github.com/auth0/express-openid-connect,因为我看到错误来自护照,WDYT?【参考方案3】:

我们遇到了类似的问题,但我们的行为更加间歇性,我们在登录 Safari 而不是 Chrome 时出现错误。

据我了解,这是因为在我们第一次进行身份验证时设置了会话 cookie,它存储了 statecode-verifier(仅在使用 PKCE 流时)和 OIDC 客户端需要验证的其他值身份验证。

但是,当 /callback URL 被命中时,浏览器会将这个会话 cookie 发送到服务器以完成身份验证。

只要这个cookie没有被发送,就是这个错误发生的时候,因为回调假设这是一个新的请求并且它崩溃了......

对我们来说,这有两种表现。

    饼干 同一地点:“松懈” 安全:真

    适用于 chrome,但这不适用于 safari

    饼干 同一站点:“无” 安全:真

    适用于 chrome 和 safari

这需要在 express-session 中间件上设置(抱歉,我不确定需要的语法)

【讨论】:

【参考方案4】:

使用瀑布异步功能可能会有所帮助。只需将 app.get 路由功能替换为以下代码。 当我们必须运行依赖于前一个任务输出的任务时,Waterfall 会很有帮助。

    app.get('/redirect', async (req, res, next) => 
        await passport.authenticate('oidc',
            async.waterfall([
                function (err,user) 
                    // print second log
                    console.log('2. ------------redirect Called!------------');
                    if (err) 
                        console.log(`Authentication failed: $err`);
                        return next(err);
                    
                    if (!user) 
                        return res.send('no identity');
                    

                    req.login(user, async (e) => 
                        if (e) 
                            console.log('not able to login', e);
                            return next(e);
                        
                        try 
                            const url = await azpi.GetUsers(user.id_token);
                            // print last log
                            console.log('3. ------------user process finished successfully----');
                            return res.redirect(url);

                         catch (er) 
                            res.send(er.message);
                        
                    );
                
            ], function (err) 
                if (err) return next(err);  //here you can check error
            )   
        );
    );

【讨论】:

谢谢,我会试一试告诉你 不,不要将 async.js 回调样式与 Promise 和 async 函数混合使用。使用单个函数传递数组不会改变任何事情! 谢谢,我已经尝试过了,得到了相同的结果,还有其他想法吗?【参考方案5】:

尝试这样使用

const key = crypto.randomBytes(16)
const mac = crypto.createMac('cmac', key,  cipher: 'aes-128-cbc' )
mac.update(crypto.randomBytes(30))
console.log(mac.final())
// => <Buffer b9>

【讨论】:

对不起,我不明白,使用什么,它与我的问题有什么关系?它如何解决问题请解释

以上是关于存在 Express 会话问题的节点的主要内容,如果未能解决你的问题,请参考以下文章

创建没有 express 的会话

Express 和 Redis 会话无法在浏览器关闭后继续存在

GraphQL 订阅、websocket、nodejs、Express 会话

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

使用 Express 和 Node,如何跨子域/主机头维护会话

每次刷新或访问页面时,Node js express-session都会创建新的会话ID