我正在通过带有 nodejs/express 后端的 SAML2 上的通行证设置单点登录。我最终陷入了某种循环..
流程如下:
当我访问我的应用时,它会调用 whoami
路由,定义如下。在第一次通过时,这应该(并且确实)返回 401(未经授权)。
当我的应用返回 401(未经授权)时,我们会将应用重定向到 login
。
下面定义的 login
路由通过 RelayState
记住我们的原始 URL(以便我们可以返回到它)并在重定向回应用程序之前运行 passport.authenticate(....)
。我承认不知道我们如何或为什么重定向回此处的应用程序 - 删除它并不能解决问题,但是,实际上,它确实会转到 SSO 提供程序(在 config.saml.options
目的)。
SSO 提供者发挥了它的魔力并返回到 /login/callback
路由,定义如下。此请求包含所有需要的信息。
......但是......然后我的应用程序重新启动该过程......创建一个循环:
WhoAmI -> 登录 -> LoginCallback -> WhoAmI -> 登录 -> LoginCallback
我的理解是,在某些时候,后端应该在本地保存用户,随后的 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()
})
}