Узел с проблемой Express сеанса - PullRequest
12 голосов
/ 05 августа 2020

Я использую следующий код, который работает. Однако после нескольких успешных вызовов (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, который является просто express сервером):

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}`));

вход в систему. 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, которая является асинхронной c функцией, и останавливается на })(req, res, next);, возможно, это проблема асинхронной c.

Мы хотим использовать этот код в prod вместо предыдущей реализации Java.

Если я могу использовать другой метод для oid c, дайте мне знать.

ОБНОВЛЕНИЕ

Каждый должен быть одним вызовом и регистрироваться в следующем порядке:

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 не происходит, только второй и ошибка.

Если есть другой способ добиться этого (другая библиотека et c), дайте мне знать.

Я нашел эту библиотеку, которая может помочь, поскольку она не использует паспорт (я хочу уменьшить deps, чтобы увидеть, откуда возникает проблема).

Когда я пытаюсь что-то вроде этого:

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

Я получаю эту ошибку: issuer response_mode supporting only "query" or "fragment", но когда я запускаю код выше (в начале сообщения) с теми же issuer и response_mode, все работает, есть идеи?

Ответы [ 4 ]

2 голосов
/ 14 августа 2020

Проблема, по-видимому, связана с состоянием гонки, при котором, если вы одновременно получаете два запроса в полете, когда один из них завершает, он очищает приготовление сеанса ie до того, как другой получит шанс завершить. Как бы то ни было, вы не единственный человек с этой этой проблемой .

Я не думаю, что это проблема самой библиотеки, но я думаю, что проблема больше в библиотека сеанса. Вы можете попробовать express-session библиотеку с параметрами saveUninitialized / resave, установленными на false, и проверить, видите ли вы по-прежнему ту же проблему, например,

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

Единственное различие между этой библиотекой и cookie-session тот, который вы используете, это express-session, только сохраняет идентификатор сеанса в Cook ie, данные хранятся на стороне сервера. Если вы обнаружите, что это работает, вы можете использовать хранилище более производственного уровня (настройка по умолчанию - работать с хранилищем в памяти).

FWIW - вам нужно только настроить стратегия один раз, глядя на то, что она делает, я был бы удивлен, если бы это часть проблемы, но я бы исправил это только в случае

0 голосов
/ 12 августа 2020

Можно подумать, что если вы решите включить сеансы, вам нужно использовать express .session () перед паспортом.session (), чтобы гарантировать, что сеанс входа пользователя будет восстановлен в правильном порядке.

См. Эту статью

0 голосов
/ 13 августа 2020

Использование asyn c функции водопада может быть полезным в этом. Просто замените функцию маршрута 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
            })   
        );
    });
0 голосов
/ 11 августа 2020

попробуйте использовать как это

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>

...