使用Fastify Passport LocalStrategy访问受保护路由时出现'Expected a string or Buffer'错误及重复请求问题
Fastify Passport LocalStrategy访问受保护路由时出现'Expected a string or Buffer'错误及重复请求问题
看起来你碰到了两个关联的问题:Payload类型错误和重复请求,咱们一步步拆解解决它们:
1. 核心误区:受保护路由误用了LocalStrategy
"local"策略是专门用来处理用户名密码登录请求的(比如POST /login),不是用来验证已登录用户的受保护路由的。你现在把它放在/protected这个GET路由的preValidation里,直接导致了两个问题:
- GET请求一般不会携带
email和password参数,Passport尝试读取这些参数时触发异常 - LocalStrategy默认会在认证失败时重定向(比如跳转到登录页),这就是你看到的“重复请求”的根源
修复方案:
把登录逻辑和路由保护逻辑分开:
- 单独写一个
POST /login路由处理用户名密码认证,用"local"策略 - 受保护的
/protected路由改用"session"策略,验证用户的登录会话
示例代码:
// 登录路由:处理邮箱密码登录 fastify.route({ method: "POST", url: "/login", preValidation: fastifyPassport.authenticate("local", { failWithError: true, // 让错误直接抛出,而非重定向 authInfo: false }), handler: async (req, res) => { return res.send({ msg: "登录成功", user: req.user }); } }); // 受保护路由:验证已登录的会话 fastify.route({ method: "GET", url: "/protected", preValidation: fastifyPassport.authenticate("session", { failWithError: true, authInfo: false }), handler: async (req, res) => { return res.send({ msg: "ok", user: req.user }); } });
2. 解决"Expected a string or Buffer"错误
这个错误通常和会话存储的序列化或配置有关,咱们排查两个点:
a. 检查fastify-session配置
确保你正确配置了@fastify/session,特别是secret必须是字符串或Buffer(这是错误提示的核心点),示例配置:
// 在注册auth插件前,先配置session await fastify.register(require("@fastify/session"), { secret: "your-strong-random-secret-here", // 必须是字符串/Buffer,生产环境用环境变量 cookie: { secure: process.env.NODE_ENV === "production", // 生产环境开启HTTPS时设为true httpOnly: true, maxAge: 86400000 // 会话有效期1天 } });
b. 修正认证回调的逻辑
在handleUserRecovery(LocalStrategy的回调)里,验证失败时要明确抛出未授权错误,而不是返回false,这样Passport能正确处理错误,避免异常序列化:
async function handleUserRecovery(req: FastifyRequest, email: string, password: string) { try { const user: User[] = await store("users").where({ email }).limit(1); if (!user || user.length < 1) { throw fastify.httpErrors.unauthorized("邮箱或密码错误"); } // 这里补充你的密码校验逻辑(比如bcrypt.compare) const isPasswordValid = await bcrypt.compare(password, user[0].password); if (!isPasswordValid) { throw fastify.httpErrors.unauthorized("邮箱或密码错误"); } // 返回整理后的用户对象,避免包含敏感字段(比如密码) const sessionUser = { id: user[0].id, email: user[0].email }; return sessionUser; } catch (err) { console.error("登录验证出错:", err); throw err; // 抛出错误让Passport处理 } }
另外,确保反序列化器返回的sessionUser是一个可序列化的普通对象,没有函数、循环引用等无法存储的内容。
3. 消除重复请求
在authenticate选项里加上failWithError: true,就能阻止Passport在认证失败时发起重定向,自然就不会有重复请求了——这一点我们在前面的路由示例里已经配置了。
最后,测试流程要正确:
- 先发送
POST /login请求,携带email和password参数,拿到会话Cookie - 用同一个Cookie发送
GET /protected请求,此时会通过Session策略验证,返回成功响应
备注:内容来源于stack exchange,提问作者semnmrr
相关产品推荐
相关产品推荐

