You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Passport req.login()是否在Session中持久化用户?SAML认证疑问

SAML认证中req.isAuthenticated()的行为疑问

我用Node.js Express开发已有两年,但Passport/SAML认证完全是新领域。相关文档稀缺、不完整还经常矛盾,官方也没有真实场景的示例,找到的示例都没法直接运行,得自己调整才能用。不过我已经实现了以WordPress作为IDP的概念验证。

现在对req.isAuthenticated()的行为有疑问:登录之后,后续的授权请求会跳过IDP,直接调用passport.deserializeUser()——这个方法只从Session里取上次保存的用户信息,只要不为空就判定已认证。问题是,当我在WordPress登出后,我的网站依然认为用户处于登录状态,直到Session过期。

这是预期行为吗?我注意到如果在ACS里不调用req.logIn(),就会跳过整个passport.serializeUser()和deserializeUser()流程,这样每次请求都会走IDP,符合我的需求,但这时我必须自定义isAuthenticated检查逻辑。我不想大量修改现有代码,希望用原生的解决方案。

请问我是不是遗漏了什么?以下是我的相关代码实现:

AuthorizationController实现

import * as express from 'express';
import { ApiRoot } from '..';
import { passport } from '../Services/Saml';
import bodyParser from 'body-parser';

class AuthorizationController {
    private _Path = '/Authorization';
    private router = express.Router();

    /**
     * Create a new instance of our AuthorizationController object.
     */
    constructor()
    {
        // Set the routes and end points. We only need one for now, the one that consumes the SAML.
        this.router.post(
            `${ApiRoot}${this._Path}/SAML/Consume`,
            bodyParser.urlencoded({ extended: false }),
            this.ConsumeSaml
        );
    }

    /****************************************************************************************/

    /**
     * Consume the SAML returned by the IDP.
     */
    private ConsumeSaml = (req: any,
                           res: express.Response,
                           next: any) => 
    {
        passport.authenticate(
            "saml", 
            {
                session: false 
            },
            (_err, user: any) =>
            {
                // We need to call req.logIn() (because this is apparently a custom ACS) so that 
                // passport serializeUser() and deserializeUser() gets called.
                req.logIn(user, (err: any) =>
                {
                    if (err)
                    {
                        return (next(err));
                    }
                    else
                    {
                        res.redirect(req.body.RelayState);
                    }
                });
            }
        )(req, res, next);
    }
}

export default AuthorizationController;

Passport SAML策略与序列化配置

import passport from "passport";
import SysTrick from "../Helpers/SysTrick";
const SamlStrategy = require('passport-saml').Strategy;

// Setup the SAML configuration.
const _SamlConfig = {
    entryPoint: "entrypoint",
    callbackUrl: `callback`,
    issuer: "issuer",
    cert: "cert",
};

// Setup the SAML stragety.
const _Strategy = new SamlStrategy(
    _SamlConfig,
    (profile, done) => {
        console.log("strategy");
        
        return (done(null, { "id": profile.nameID }));
    }
);

// Setup passport.
passport.use('saml', _Strategy);

passport.serializeUser((user: any, done) => {
    console.log(`serializeUser| id = ${user.id}`);

    done(null, user.id);
});

passport.deserializeUser((id, done) => {
    console.log(`deserializeUser| id = ${id}`);

    return (done(null, (id ? { "id" : id } : null)));
});

export { passport };

服务器初始化代码

this._App.use(bodyParser.urlencoded({ extended: false }))
this._App.use(bodyParser.json())

// Session configuration.       
const sessionConfig: any = {
    secret: "secret",
    saveUninitialized: false,
    cookie: { 
        maxAge: 1000 * 60 * 60 * 2 
    },
    resave: false,
    store: new this._FileStore({
        path: './sessions/',
    }),
}
if (SysTrick.IsProduction) 
{
    this._App.set('trust proxy', 1);        //.. trust first proxy
    sessionConfig.cookie.secure = true;     //.. serve secure cookies (can only be used with https clients)
}

// Enable cors if we are in development.
if (!SysTrick.IsProduction)
{
    this._App.use(cors());
}

// Limit the size of the inload that will be processed.
this._App.use(express.json({ limit: '15mb' }));

// Add security.
this._App.use(helmet({
    contentSecurityPolicy: false,           //.. easiest way to allow external scripts (e.g., for awsomefonts) to run
    frameguard: false                       //.. allow views to show in iframes
}));

// Have Node serve our React application (views).
this._App.use(express.static(App.ViewsPath));

// Setup the Express global error handler.
this._App.use(
    function(error: any, _req: express.Request, res, next)
    {
        // Log the error.
        try   { Log.Error(error, "Global Error Handler"); }
        catch { /* */ }

        // Display the error on the console.
        console.log(chalk.red.bold("ERROR"));
        console.log(chalk.red.bold("====="));
        console.log(error);

        // Because we hook post-response processing into the global error handler, we get
        // to leverage unified logging and error handling; but, it means the response may
        // have already been committed, since we don't know if the error was thrown PRE or
        // POST response. As such, we have to check to see if the response has been
        // committed before we attempt to send anything to the user.
        if (!res.headersSent)
        {
            res.status(500).send("Internal server error.");
        }
    }
);

// Add the session storage.
this._App.use(session(sessionConfig));

// Add passport for SAML SSO.
this._App.use(passport.initialize());
this._App.use(passport.session());

路由与认证中间件

// Test public route.
this._App.get(
    "/public",
    CheckAuthorization,
    (req, res, next) => 
    {
        res.send("I have been authorised!");
    }
);

// This route is for passport-SAML authorization via WordPress.
this._App.get(
    App.LoginUrl,
    (req: any, res, next) => 
    {
        // We set the req.query.RelayState, after using session in the Check-Auth()
        // middlware. The reason why we have to use req.query.RelayState and not session
        // all the way to the ACS is because the session value is undefined for some reaon
        // in the ACS.
        req.query.RelayState = req.session.OriginalUrl;

        passport.authenticate('saml')(req, res, next);
    }
);

// This middleware is for protecting routes with with passport-SAML authorization.
function CheckAuthorization(req: any, res: any, next: any)
{
    // The IDP is bypassed, constantly calling passport.deserializeUser(), which has a valid user
    // if the session hasn't ended. Will have to override this checker.
    if (req.isAuthenticated())
    {
        next();
    } 
    else
    {
        // NOTE: We use session here because saving to req.query.RelayState does not
        // work from this middleware. It is in the actual route that calls 
        // passport.authenticate() that req.query.RelayState works. This is all an ugly
        // hack. This whole SAML business is a hack, and proper documentation/example is
        // non-existent.
        req.session.OriginalUrl = req.originalUrl;

        // Redirect to the login route, which is really a call to the IDP.
        res.redirect(App.LoginUrl);
    }
}

内容的提问来源于stack exchange,提问作者abc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 17:54:53