как мне перенаправить обратно на первоначально запрошенный URL-адрес после аутентификации с помощью passport-saml?

#node.js #express #passport.js #saml-2.0

#node.js #экспресс #passport.js #saml-2.0

Вопрос:

Извините, если это глупый вопрос, но у меня возникли некоторые проблемы с пониманием того, как я мог бы перенаправить клиентский браузер обратно на тот URL, который был первоначально запрошен после успешной аутентификации с нашим поставщиком идентификационных данных SAML (IdP). Я использую последние версии passport-saml, passport и express.

Например, предположим, что клиент первоначально запросил /foo/bar ссылку на другой незащищенной странице, но поскольку это защищенный ресурс, я отвечаю перенаправлением на /login , куда я и звоню passport.authenticate('saml') .

 app.get('/login', passport.authenticate('saml'));

function ensureAuth(req, res, next) {
    if (req.user.isAuthenticated()) {return next();}
    else {res.redirect('/login');}
}

app.get('/foo/bar', ensureAuth, function(req, res) {
    ...
});
  

Этот вызов перенаправит браузер на страницу входа моего IdP, и после успешной аутентификации IdP отправит сообщения обратно на мой /login/callback маршрут. В этом маршруте я снова использую passport.authenticate(saml) для проверки ответа SAML, и если все в порядке, я затем перенаправляю браузер обратно на запрошенный ресурс…но как мне узнать, что это был за запрошенный ресурс? Поскольку это обратный вызов POST, я потерял любое состояние, связанное с исходным запросом.

 app.post('/login/callback', passport.authenticate('saml'), function(req, res) {
    res.redirect('...can I know which url to redirect back to?...');
});
  

Пример в passport-saml readme просто показывает жестко запрограммированное перенаправление обратно на корневой ресурс, но я бы хотел перенаправить обратно на первоначально запрошенный URL ( /foo/bar ).

Могу ли я отправить URL-адрес или какое-либо другое значение IdP, которое будет обработано по кругу и отправлено обратно в ответ SAML? И если да, то как я могу получить к нему доступ по моему /login/callback маршруту?

Или есть какой-то лучший способ express / passport сделать это, которого мне не хватает?

Я был бы признателен за любую помощь, которую вы можете предоставить!

Комментарии:

1. сохраните исходный URL-адрес запроса в кэше сеанса, срок действия которого истекает через 10 минут. затем извлеките его и уничтожьте при успешном перенаправлении пользователя в процессе

2. Ах, да, это сработало бы! Спасибо за идею. Однако было бы намного чище, если бы я мог отправить некоторое состояние IdP и получить его обратно. Я просмотрел ссылку на спецификацию SAML 2.0, и похоже, что вы можете указать другой обратный вызов для каждого запроса на аутентификацию через элемент AssertionConsumerServiceURL , но там отмечается, что «Ответчик ДОЛЖЕН каким-то образом убедиться, что указанное значение действительно связано с запрашивающим» (49). Возможно, указание разных значений каждый раз на самом деле не работает на практике.

3. ну, вам нужно всегда проверять URL-адрес ссылки. в противном случае хакеры воспользуются вашей схемой для перенаправления на вредоносное ПО. т.е. они получают электронное письмо, выдаваемое за ваш домен, со ссылкой на какой-либо сайт, который перенаправляет на вашу страницу входа. при входе в систему они перенаправляются обратно к рефереру, крадя файлы cookie ваших пользователей.

4. Да, я подумал, что можно использовать разные URL обратного вызова в пределах одного домена. Я ожидал бы, что IdP отклонит любой запрос аутентификации, в котором URL обратного вызова был из домена, отличного от того, который зарегистрирован в IdP.

Ответ №1:

Могу ли я отправить URL-адрес или какое-либо другое значение IdP, которое будет обработано по кругу и отправлено обратно в ответ SAML? И если да, то как я могу получить к нему доступ по моему маршруту / login / callback?

Для возврата значения через IdP вам необходимо использовать RelayState . Это значение, которое вы можете отправить IdP, и, если вы это сделаете, они обязаны отправить его обратно без изменений.

Вот что говорят спецификации SAML:

3.1.1 Использование RelayState

Некоторые привязки определяют механизм «RelayState» для сохранения и передачи информации о состоянии. Когда такой механизм используется при передаче сообщения запроса в качестве начального шага протокола SAML, он предъявляет требования к выбору и использованию привязки, впоследствии используемой для передачи ответа. А именно, если сообщение запроса SAML сопровождается данными RelayState, то ответчик SAML ДОЛЖЕН вернуть свой ответ протокола SAML, используя привязку, которая также поддерживает механизм RelayState, и он ДОЛЖЕН поместить точные данные RelayState, полученные с запросом, в соответствующий параметр RelayState в ответе.

Чтобы использовать это с passport-saml, вы должны добавить его в качестве additionalParams значения. Приведенный ниже код показывает, что это происходит.

 saml = new SamlStrategy
    path: appConfig.passport.saml.path
    decryptionPvk: fs.readFileSync(appConfig.passport.saml.privateKeyFile)
    issuer: appConfig.passport.saml.issuer
    identifierFormat: tenant.strategy.identifierFormat
    entryPoint: tenant.strategy.entryPoint
    additionalParams:{'RelayState':tenant.key}
    ,
    (profile, next) -> 
        # get the user from the profile
  

Приведенный выше код взят из многопользовательской реализации saml, поэтому я отправляю свой, tenant.key как RelayState параметр. Затем я извлекаю это значение из тела отправленного возврата от IdP и использую его для восстановления всего необходимого мне состояния.

 getTenantKey: (req, next) ->
    key = req.body?.RelayState ? routes.match(req.path).params.tenentKey
    next null, key
  

Ваш случай может быть проще. Вероятно, вы захотите сохранить URL-адрес конечного назначения в кэше с ограниченным временем, а затем отправить ключ кэша в качестве RelayState параметра.

Как бы то ни было, вы можете вообще избежать использования RelayState , если просто используете исходный идентификатор запроса SAML в качестве ключа кэша. Это значение всегда возвращается вам через InResponseTo поле.

Комментарии:

1. Как вы создали многопользовательский saml? Что-то не в NPM или вы сами это сделали? В конечном итоге мне потребуется поддерживать несколько поставщиков удостоверений.

2. Возможно ли установить RelayState после создания стратегии? В начале загрузки моего сервера у меня есть «passport.use(новая стратегия SamlStrategy(«, как у вас выше. Во время выполнения у меня есть «passport.authenticate(«… Возможно ли тогда предоставить RelayState? Я бы лучше знал во время выполнения, что должно быть возвращено.

3. @Chris вы хотя бы нашли ответ на свой вопрос: «Возможно ли установить RelayState после создания стратегии»? Мне интересно то же самое.

4. @That1guyoverthr Я так и не нашел хорошего решения. Я восстанавливал passport при каждом вызове, чтобы ввести новое состояние ретрансляции для этого пользователя. Однако это состояние ретрансляции было одноэлементным, и если пользователи нажмут на него одновременно, можно будет получить состояние ретрансляции другого пользователя! Я сдался и использую session, чтобы определить, где находится пользователь, прежде чем он попытается войти в систему.

5. @MForMarlon После некоторых сражений я отказался от состояния ретрансляции и просто использую session. По сути, я сохраняю местоположение пользователя в сеансе, когда он пытается войти в систему, а затем получаю доступ к тому же сеансу, когда он возвращается. Мне это не нравится, но я видел, как это делает Java.

Ответ №2:

Если вы хотите указать RelayState для каждого запроса, вы могли бы подумать, что могли бы сделать:

 passport.authenticate('saml', { additionalParams: { RelayState: "foo" } })
  

Но это не работает. Если вы посмотрите на реализацию: https://github.com/bergie/passport-saml/blob/6d1215bf96e9e352c25e92d282cba513ed8e876c/lib/passport-saml/saml.js#L326 вы увидите, что дополнительные параметры выбираются из параметров начальной конфигурации или из объекта req (но не из параметров для каждого запроса).

Но, к счастью, стратегия Passport SAML выполняет это для каждого запроса:

 var RelayState = req.query amp;amp; req.query.RelayState || req.body amp;amp; req.body.RelayState;
  

Затем он ищет в параметрах конфигурации дополнительные (глобальные) параметры.

Итак, если вы хотите указать параметры RELAY для каждого запроса, вы должны загрузить их в req перед вызовом authorize . В моем случае я делаю что-то вроде этого внутри обработчика маршрута:

 req.query.RelayState = req.params.redirect_to;
passport.authenticate('saml')(req, res, next);
  

Затем, когда перенаправленный запрос возвращается от SAML IdP, вы должны иметь возможность использовать req.body.Передайте параметры для доступа к вашему состоянию для каждого запроса.

Обратите внимание, что если ваше значение RelayParams является объектом, возможно, вам придется использовать JSON.stringify для кодирования его в RelayParams и JSON.parse для декодирования его обратно (в моем случае мне пришлось это сделать).

Комментарии:

1. Спасибо, это именно то, что мне было нужно, чтобы заставить это работать.

2. Спасибо за это решение. Я искал что-то вроде этого.

3. Для меня это сработало идеально. Принятый ответ помещает его в стратегию, которая не является динамической для каждого запроса. Это решение помещает параметр в запрос. запрос. RelayState, который вы затем можете получить в сообщении обратного вызова из req.body. Параметры ретрансляции. Идеально!

4. Это работает, и abd должен быть ответом для обработки каждого запроса. Свойство RelayState очень важно