微信扫码登录 iframe 方案中的状态拦截陷阱
背景
在 Web 端实现微信扫码登录时,常见的方案是使用 iframe 嵌入微信二维码页面。用户扫码授权后,iframe 内部会重定向到我们配置的回调页面,回调页面再通过 postMessage 通知父页面完成登录。
最近在给登录流程增加「用户协议勾选」功能时,遇到了一个有趣的问题:用户勾选协议后扫码,在手机上确认授权前又取消了勾选,结果登录流程依然执行了。
问题现象
预期行为:用户取消勾选协议 → 拦截登录流程 → 不跳转
实际行为:用户取消勾选协议 → 控制台显示"未同意协议,不触发事件" → 页面依然跳转了
架构分析
整个微信登录的组件结构如下:
scss
Login.vue (页面)
└── Container.vue
└── wxQrCodeLogin.vue
└── iframe (微信二维码)
└── WxLogin.vue (回调页面,iframe 内部)
登录流程:
- 用户勾选协议 → 显示二维码(iframe)
- 用户手机扫码 → 微信授权页面
- 用户确认授权 → iframe 重定向到
WxLogin.vue WxLogin.vue调用后端接口获取 token- 通过
postMessage通知父页面 - 父页面完成登录跳转
问题根因
在 wxQrCodeLogin.vue 中,我添加了协议状态拦截:
javascript
window.addEventListener("message", (msg) => {
// 未勾选协议,直接返回
if(!isAgree.value) {
console.log("未同意协议,不触发事件");
return;
}
if(msg.data.type === '1') {
emit('qrLoginSuccess', msg.data.token);
}
});
看起来没问题,但实际上拦截失效了。原因在 WxLogin.vue(iframe 内的回调页面):
javascript
if(token) {
Store.set_cookie('token', token); // 问题在这里!
window.parent.postMessage({ type: '1', token }, '*');
}
iframe 内部直接设置了 cookie!
由于 iframe 和父页面同域,cookie 是共享的。当 token 被写入 cookie 后,主站的登录状态检测逻辑检测到 token,自动触发了页面跳转。
整个过程:
- 微信授权成功 → iframe 内
WxLogin.vue执行 Store.set_cookie('token', token)→ cookie 已写入postMessage发送给父页面- 父页面
isAgree检查 → 返回,不处理 - 但 cookie 已经存在 → 主站检测到登录状态 → 跳转
拦截的是 postMessage,但 cookie 的写入发生在 postMessage 之前,根本拦不住。
解决方案
核心原则
iframe 回调页面只负责「中转」,不应该直接操作登录状态(cookie、localStorage 等)。状态的写入应该由父页面根据业务逻辑决定。
代码修改
WxLogin.vue(iframe 回调页面):
javascript
// 修改前
if(token) {
Store.set_cookie('token', token); // 删除这行
window.parent.postMessage({ type: '1', token }, '*');
}
// 修改后
if(token) {
// 只传递 token,不设置 cookie
window.parent.postMessage({ type: '1', token }, '*');
}
父页面在收到 postMessage 后,根据 isAgree 状态决定是否设置 cookie 并完成登录:
javascript
window.addEventListener("message", (msg) => {
if(!isAgree.value) {
// 可以弹出协议确认弹窗,让用户选择
return;
}
if(msg.data.type === '1') {
// 在这里设置 cookie
await loginCallback({ token: msg.data.token });
emit('qrLoginSuccess', msg.data.token);
}
});
延伸思考
为什么 v-show 不能解决问题?
最初尝试用 v-show 隐藏 iframe,但 v-show 只是 display: none,iframe 依然存在,内部的回调逻辑照常执行。
为什么 v-if 也有问题?
v-if 会销毁 iframe,但如果用户已经扫码进入微信授权页面,此时销毁 iframe 再重建,新的 iframe 无法接收之前扫码的授权回调,用户需要重新扫码。
最佳实践
- iframe 回调页面职责单一 :只负责接收授权结果、调用后端接口、通过
postMessage传递数据 - 状态操作由父页面控制:cookie、localStorage、页面跳转等操作都应该在父页面根据业务状态决定
- 考虑异步流程中的状态变化:用户可能在异步操作过程中改变状态,设计时要考虑这种边界情况
总结
这个问题的本质是职责划分不清晰导致的。iframe 内的回调页面越权操作了本应由父页面控制的登录状态,使得父页面的拦截逻辑形同虚设。
在设计跨窗口/跨 iframe 通信的功能时,要明确各个组件的职责边界,状态的写入和业务逻辑的执行应该集中在一个地方,避免分散导致的控制失效。