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

如何在Passport.js中取消封禁用户的序列化并实现重定向?

在Passport.js的serializeUser阶段处理用户封禁并实现重定向的方案

我有一个配置了express-session和Passport.js的Express应用,用户通过Steam策略登录。需求是在passport.serializeUser()阶段调用数据库检查用户是否被封禁,如果用户被封禁,就不创建会话,并重定向到运行在其他端口的前端页面。

当前代码如下:

passport.use(new (SteamStrategy as any)({
    returnURL: `${process.env.API_URL}/login/callback`,
    realm: process.env.API_URL,
    apiKey: process.env.STEAM_API_KEY,
}, (identifier: any, profile: any, done: any) => {
    return done(null, profile);
}));

passport.serializeUser(async (userdata: any, done) => {
    const steamAge = userdata._json.timecreated;
    const avatar = userdata.photos[2].value
    const { id: steamId, displayName } = userdata;

    const data = await user.getBySteamIdOrCreate(steamId, { steamId, displayName, avatar, steamAge })

    if ("steamId" in data) {

        // Don't create session if user is blocked
        if (data.isBlocked) {
            // Redirect here somehow
            done("my custom error", null)
            return
        }
        // Create session if not blocked
        done(null, data.steamId);
    } else {
        done(null, false);
    }

});
passport.deserializeUser(async (steamid: string, done) => {
    const data = await user.getBySteamId(steamid)

    if ("steamId" in data!) {
        done(null, data.steamId);
    } else {
        done(null, false);
    }
});

现在的问题是,当调用done("my custom error", null)时,页面只会显示空白错误页,而我需要实现重定向,但serializeUser里无法直接访问res对象调用res.redirect(),求可行的解决方案。


方案1:在登录回调路由中处理封禁检查和重定向

把用户封禁的检查逻辑从serializeUser移到Passport登录的回调路由里,这样就能直接访问req和res对象,灵活实现重定向。

修改步骤:

  1. 在Steam策略的verify回调中提前查询并返回用户完整数据
  2. 在回调路由里判断用户是否被封禁,若封禁则清除会话并重定向,否则正常完成登录

示例代码:

// 修改Steam策略的verify回调,返回用户完整数据
passport.use(new (SteamStrategy as any)({
    returnURL: `${process.env.API_URL}/login/callback`,
    realm: process.env.API_URL,
    apiKey: process.env.STEAM_API_KEY,
}, async (identifier: any, profile: any, done: any) => {
    const steamAge = profile._json.timecreated;
    const avatar = profile.photos[2].value;
    const { id: steamId, displayName } = profile;
    const userData = await user.getBySteamIdOrCreate(steamId, { steamId, displayName, avatar, steamAge });
    return done(null, userData);
}));

// 简化serializeUser,仅在用户未被封禁时存储steamId
passport.serializeUser((userdata: any, done) => {
    if (!userdata.isBlocked) {
        done(null, userdata.steamId);
    } else {
        done(null, false); // 不创建会话
    }
});

// 登录回调路由
app.get('/login/callback', 
    passport.authenticate('steam', { failureRedirect: '/login' }),
    (req, res) => {
        // 直接访问用户数据判断封禁状态
        if (req.user && (req.user as any).isBlocked) {
            // 清除已创建的会话
            req.logout((err) => {
                if (err) console.error(err);
                // 重定向到前端封禁提示页面
                res.redirect('http://localhost:3000/blocked');
            });
        } else {
            // 正常登录,跳转前端首页
            res.redirect('http://localhost:3000/');
        }
    }
);

方案2:自定义Passport认证中间件捕获错误并重定向

通过自定义认证中间件,接管Passport的回调逻辑,捕获serializeUser抛出的自定义错误,直接在中间件里实现重定向。

步骤:

  1. 在serializeUser中传递带标识的自定义错误对象
  2. 实现自定义认证中间件,捕获该错误后执行重定向

示例代码:

// 修改serializeUser的错误处理,传递自定义错误
passport.serializeUser(async (userdata: any, done) => {
    const steamAge = userdata._json.timecreated;
    const avatar = userdata.photos[2].value
    const { id: steamId, displayName } = userdata;

    const data = await user.getBySteamIdOrCreate(steamId, { steamId, displayName, avatar, steamAge })

    if ("steamId" in data) {
        if (data.isBlocked) {
            // 自定义错误,带上标识和状态码
            const blockedError = new Error('USER_BLOCKED');
            (blockedError as any).statusCode = 403;
            done(blockedError, null);
            return
        }
        done(null, data.steamId);
    } else {
        done(null, false);
    }
});

// 自定义Steam认证中间件
const authenticateSteam = (req: any, res: any, next: any) => {
    passport.authenticate('steam', (err: any, user: any, info: any) => {
        // 捕获用户封禁的自定义错误
        if (err && err.message === 'USER_BLOCKED') {
            return res.redirect('http://localhost:3000/blocked');
        }
        // 处理其他错误
        if (err) return next(err);
        // 处理认证失败
        if (!user) return res.redirect('/login');
        
        // 正常登录,建立会话
        req.logIn(user, (loginErr) => {
            if (loginErr) return next(loginErr);
            return res.redirect('http://localhost:3000/');
        });
    })(req, res, next);
};

// 使用自定义中间件作为登录回调路由
app.get('/login/callback', authenticateSteam);

方案3:在deserializeUser中处理(备选,不推荐)

如果必须在序列化流程中处理,可以在deserializeUser里检查用户状态,若封禁则标记用户无效,再通过全局中间件清除会话并重定向。但这种方式会先创建会话再销毁,效率不如前两种。

示例代码:

passport.deserializeUser(async (steamid: string, done) => {
    const data = await user.getBySteamId(steamid);
    if ("steamId" in data!) {
        if (data.isBlocked) {
            done(null, false); // 标记用户无效
        } else {
            done(null, data.steamId);
        }
    } else {
        done(null, false);
    }
});

// 全局中间件检查登录用户状态
app.use((req, res, next) => {
    if (req.isAuthenticated() && !(req.user as any).steamId) {
        req.logout((err) => {
            if (err) console.error(err);
            res.redirect('http://localhost:3000/blocked');
        });
    } else {
        next();
    }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 23:30:45