如何在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对象,灵活实现重定向。
修改步骤:
- 在Steam策略的verify回调中提前查询并返回用户完整数据
- 在回调路由里判断用户是否被封禁,若封禁则清除会话并重定向,否则正常完成登录
示例代码:
// 修改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抛出的自定义错误,直接在中间件里实现重定向。
步骤:
- 在
serializeUser中传递带标识的自定义错误对象 - 实现自定义认证中间件,捕获该错误后执行重定向
示例代码:
// 修改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
相关产品推荐
相关产品推荐

