求助:跨域场景下登录弹窗与父页面的Cross Window Communication问题
跨域登录弹窗通信与全站请求头设置解决方案
核心问题拆解
你遇到的是跨域场景下弹窗与父页面的通信障碍,以及登录后全站请求头的统一注入需求,以下是几种可行的解决方案:
方案1:使用postMessage实现跨域通信(推荐)
这是浏览器原生支持的跨域通信API,只要两端配合验证来源,就能安全传递数据。
父页面(主站)代码
// 打开登录弹窗并保存实例 const loginPopup = window.open('https://your-auth-domain.com/login', 'loginWindow', 'width=500,height=600'); // 监听弹窗发来的消息 window.addEventListener('message', (event) => { // 严格验证消息来源,防止恶意攻击 if (event.origin !== 'https://your-auth-domain.com') return; // 处理登录成功的消息 if (event.data.type === 'LOGIN_SUCCESS') { const { userInfo, authToken } = event.data; // 关闭弹窗 loginPopup.close(); // 全站注入请求头(以axios为例) axios.interceptors.request.use(config => { config.headers['Authorization'] = `Bearer ${authToken}`; config.headers['X-User-ID'] = userInfo.id; // 按需添加其他用户信息头 return config; }); // 可选:刷新页面或更新页面登录状态 window.location.reload(); } });
弹窗页面(认证域)代码
// 凭证验证成功后执行 async function handleLoginSuccess() { const response = await fetch('https://your-auth-domain.com/api/verify', { method: 'POST', body: JSON.stringify(credential), headers: { 'Content-Type': 'application/json' } }); if (response.ok) { const { userInfo, authToken } = await response.json(); // 向父页面发送消息,指定目标域名限制接收范围 window.opener.postMessage( { type: 'LOGIN_SUCCESS', userInfo, authToken }, 'https://your-main-domain.com' ); // 延迟关闭弹窗,确保消息发送完成 setTimeout(() => window.close(), 100); } }
方案2:服务器端中转(兼容旧场景)
如果无法使用postMessage,可以通过服务器重定向到父域的回调页面来传递数据(注意敏感信息必须加密)。
步骤1:认证服务器重定向
凭证验证成功后,服务器返回重定向响应:
HTTP/1.1 302 Found Location: https://your-main-domain.com/login-callback?token=ENCRYPTED_TOKEN&user=ENCRYPTED_USER_INFO
步骤2:父域回调页面代码(login-callback.html)
window.addEventListener('load', () => { // 解析URL参数(注意解密) const params = new URLSearchParams(window.location.search); const authToken = decrypt(params.get('token')); const userInfo = JSON.parse(decrypt(params.get('user'))); // 调用父页面提前定义的方法 window.opener.setLoginCredentials(authToken, userInfo); // 关闭回调页面 window.close(); });
步骤3:父页面提前定义处理方法
function setLoginCredentials(authToken, userInfo) { // 存储凭证到localStorage,方便后续请求调用 localStorage.setItem('authToken', authToken); localStorage.setItem('userInfo', JSON.stringify(userInfo)); // 全站注入请求头 axios.interceptors.request.use(config => { config.headers['Authorization'] = `Bearer ${authToken}`; config.headers['X-User-ID'] = userInfo.id; return config; }); // 更新页面状态 window.location.reload(); }
方案3:同根域子域名专属方案
如果主站和认证站是同根域(如a.example.com和b.example.com),可以通过统一document.domain+localStorage+storage事件实现通信:
弹窗页面代码
document.domain = 'example.com'; localStorage.setItem('loginInfo', JSON.stringify({ userInfo, authToken })); window.close();
父页面代码
document.domain = 'example.com'; window.addEventListener('storage', (event) => { if (event.key === 'loginInfo') { const { userInfo, authToken } = JSON.parse(event.newValue); // 注入请求头... // 清理localStorage避免残留 localStorage.removeItem('loginInfo'); } });
注意:此方法仅适合同根域子域名,跨顶级域会被浏览器安全策略阻止。
全站请求头设置通用方法
无论用哪种通信方式,拿到凭证后都可以通过封装请求工具来统一注入头:
- Axios:使用请求拦截器(如上述示例)
- 原生Fetch:封装工具函数
function authenticatedFetch(url, options = {}) { const authToken = localStorage.getItem('authToken'); const userInfo = JSON.parse(localStorage.getItem('userInfo')); options.headers = { ...options.headers, 'Authorization': `Bearer ${authToken}`, 'X-User-ID': userInfo?.id }; return fetch(url, options); }
之后全站使用authenticatedFetch代替原生fetch即可。
内容的提问来源于stack exchange,提问作者SoJS
相关产品推荐
相关产品推荐

