#node.js #passport.js #saml-2.0
Вопрос:
Я настраиваю единый вход с паспортом через SAML2 с помощью серверной части nodejs/express. Я оказываюсь в какой-то петле..
Процесс происходит следующим образом:
Когда я захожу в свое приложение, оно вызывает whoami
маршрут, определенный ниже. При первом проходе это должно (и возвращает) 401 (несанкционированный). Когда это вернется с 401 (несанкционированным) в мое приложение, мы перенаправим приложение на login
. login
Маршрут, определенный ниже, запоминает наш исходный URL-адрес (чтобы мы могли вернуться к нему) RelayState
и запускается passport.authenticate(....)
перед перенаправлением обратно в приложение. Я признаю, что не знаю, как и почему мы перенаправляем обратно в приложение здесь — и удаление его не решает проблему, однако на самом деле оно обращается к поставщику единого входа (определенному в config.saml.options
объекте). Поставщик единого входа творит чудеса и возвращается к /login/callback
маршруту, определенному ниже. В этом запросе содержится вся необходимая информация. … однако… затем мое приложение перезапускает процесс… создавая цикл: WhoAmI -> Вход ->> Обратный вызов ->>> WhoAmI ->>>> Вход — > > > > Обратный вызов
Насколько я понимаю, в какой-то момент серверная часть должна сохранять пользователя локально, и последующий whoami
вызов вернет желаемую информацию.
Как я могу остановить эту циклическую обработку.
Мой passport.js файл:
import fs from 'fs';
import passport from 'passport';
import { Strategy } from 'passport-saml';
import config from './config.js';
const savedUsers = [];
passport.serializeUser((expressUser, done) => {
done(null, expressUser)
});
passport.deserializeUser((expressUser, done) => {
done(null, expressUser)
});
passport.use(
new Strategy(
{
issuer: config.saml.issuer,
protocol: 'https://',
path: '/auth/login/callback',
entryPoint: config.saml.entryPoint,
cert: fs.readFileSync(config.saml.cert, 'utf-8'),
authnContext: ["urn:federation:authentication:windows"],
identifierFormat: null, //if omitted the identifier format defaults to urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
},
(expressUser, done) => {
if (savedUsers.includes(expressUser)) {
savedUsers.push(expressUser)
}
return done(null, expressUser)
}
)
)
Мои определения маршрутов для аутентификации SAML следующие:
import express from 'express';
import config from '../config/config.js';
import '../config/passport.js'
import session from 'express-session'
import passport from 'passport'
const samlRouter = express.Router();
samlRouter.use(session(config.session));
samlRouter.use(passport.initialize());
samlRouter.use(passport.session());
samlRouter.use((req, res, next) => {
console.log("Use");
res.header('Access-Control-Allow-Origin', req.header('origin'));
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-Width, Content-Type, Accept, Authorization');
res.header('Access-Control-Allow-Credentials', 'true'); //SAML
if (req.method == 'OPTIONS') {
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET');
return res.status(200).json({})
}
next();
})
samlRouter.get('/whoami', (req, res, next) => {
console.log('whoami');
if (!req.isAuthenticated()) {
return res.status(401).json({
message: 'Unauthorized',
});
}
return res.status(200).json({ user: req.user });
});
samlRouter.get('/login',
(req, res, next) => {
console.log('login');
req.query.RelayState = req.query.origURL;
passport.authenticate('saml', config.saml.options)(req, res, next)
},
(req, res, next) => {
return res.redirect(`${config.app.location}:3000`);
}
);
samlRouter.post('/login/callback',
(req, res, next) => {
console.log('login/callback');
passport.authenticate('saml', config.saml.options)(req, res, next)
},
(req, res, next) => {
if(req.user) {
console.log('req.user.nameID :>> ', req.user?.nameID); **// THIS OUTPUTS THE CORRECT INFO**
} else {
console.log('req.user was falsey');
}
console.log(`redirect to Relay state ${req.body.RelayState}`);
return res.redirect(`${req.body.RelayState}`);
});
export default samlRouter;
Мой код приложения для получения пользователя выглядит следующим образом:
export function getUser() {
console.log('Getting user...');
const RedirectToLogin = () => {
console.log("Redirecting to xxxx/login...")
window.location.assign(`https://xxxx/login?origURL=${encodeURIComponent(window.location.href)}`)
}
return axios({
method: 'GET',
url: 'https://xxxx/whoami',
withCredentials: true
})
.then(resp => { // WE NEVER GET HERE...
if (resp.data.user) {
console.log(`User Authenticated as ${resp.data.user.nameID}`);
return Promise.resolve(resp.data.user.nameID)
}
else {
console.warn("Unauthenticated - redirecting to login")
RedirectToLogin()
}
})
.catch(err => {
console.log('err :>> ', err);
console.warn("Unauthenticated -- redirecting to login")
RedirectToLogin()
})
}
Комментарии:
1. Мне интересно посмотреть, как вы это решили!
2. Я не уверен, что сделал это правильно, но эффективно в приложениях, которые я называю, я включаю отпечаток пальца браузера. Серверная часть ищет в таблице этот отпечаток пальца. Если он найдет его, он просто вернет правильную информацию. Если это не так, он продолжает процесс единого входа. … таким образом, во второй раз приложение отправляет отпечаток пальца, и серверная часть знает, кто этот человек… без необходимости продолжать цикл единого входа.