前言
最近项目中使用了 iframe 标签进行嵌套子系统时,遇到了无法登录、而且不报错的问题。本文记录自己的踩坑过程,也希望帮助到其他同学。
需求
有两个项目,a项目 和 b项目。 当前开发的 a 项目中需要嵌套展示已上线的 b 项目内容; 且要求 b 项目的功能可以正常使用。
遇到问题
在 a 项目中直接使用 iframe 标签进行嵌套 b 项目来展示。且两个项目部署在不同的服务器下。
- a 项目:88 服务器
- b 项目:89 服务器
调试中a 项目可以正常跳转到 b 项目,也能在 iframe 中展示出来, 直接跳转到b 项目的 login 页面后正常调用了登录接口, 且登录接口 200 通了, 返回了用户信息。
但是到这里就没有任何执行了,不会在登录成功后跳转到 b项目的系统首页。
问题分析
页面也没有任何报错,接口也都是通的。 卧槽? 这什么问题?
经过反复测试确实没有任何报错,接口也通了,token等用户信息也正常拿到了,就是不跳转 b 系统首页。
但是我发现当 ifreame 的 url 加载 b项目地址成功显示出 login 登录页面后,点击登录时,页面还会正常走登录接口,此时页面会进行刷新一下 ,也可以获取到 token 等信息,但还是不跳转对应首页。
此时我第一想法就是可能登录成功后被拦截了,导致无法跳转到 home 页。 难道是服务器给拦截了吗?
于是我上网查资料,得到的结果大部分都说是:这种 iframe 嵌套场景因为浏览器的限制,一些 cookie 信息会被拦截导致重定向到了 login 无法正常登录跳转。
问题原因
-
第三方 Cookie 限制(主要问题)
- 现代浏览器(Chrome >=80, Safari, Firefox)默认阻止跨域 iframe 的第三方 Cookie
- 登录依赖的 Session/Cookie 被浏览器阻止写入
-
同源策略限制
- iframe 加载的页面与父页面不同源时,无法共享认证状态
-
X-Frame-Options 响应头
- 目标页面可能设置了
X-Frame-Options: SAMEORIGIN阻止嵌入
- 目标页面可能设置了
-
登录逻辑差异
- iframe 环境可能导致 JS 执行上下文差异(如
window.top获取失败等问题)
- iframe 环境可能导致 JS 执行上下文差异(如
关键代码
先开始我想到这个项目没用到 cookie 相关内容啊。 为了确定,于是尝试在项目里全局查找一下 cookie看看,果然发现了一个文件中包含如下代码:
js
import Cookies from 'js-cookie'
const TokenKey = 'saber-access-token'
const RefreshTokenKey = 'saber-refresh-token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function getRefreshToken() {
return Cookies.get(RefreshTokenKey)
}
export function setRefreshToken(token) {
return Cookies.set(RefreshTokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
export function removeRefreshToken() {
return Cookies.remove(RefreshTokenKey)
}
发现该项目使用了 js-cookie 来进行存储 token 等用户信息。
问题解决
经过踩坑,查资料总结一下几种解决方案:
方案1: 确保同源或同站
如果条件允许的话,让 iframe 子内容 和 父页面系统 使用相同的域名(或者至少是相同的顶级域名+相同的协议):
- 父页面系统:
https://app.example.com - iframe 子系统:
https://service.example.com
方案2: 服务器设置 SameSite
主动设置 SameSite 为 Set-Cookie:Key=Value; SameSite=None; Secure,确保协议为安全协议https
- Java (Servlet) 配置示例
java
Cookie cookie = new Cookie("sessionid", "12345");
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookie.setAttribute("SameSite", "None");
response.addCookie(cookie);
- Nginx 配置示例
arduino
location / {
proxy_pass http://backend;
proxy_cookie_path / "/; SameSite=None; Secure";
}
方案3: 禁用浏览器 SameSite 默认限制 (仅测试用)
这只适用于开发和测试环境,不要在生产环境中使用。
- Chrome 启动参数
ini
chrome --disable-features=SameSiteByDefaultCookies
- 注意事项
- 必须使用 HTTPS :
SameSite=None必须与Secure属性一起使用,网站必须使用 HTTPS - 浏览器兼容性:检查需要支持的浏览器版本是否支持 SameSite 属性
- 测试验证:在 Chrome 开发者工具的 Application > Cookies 部分检查 Cookie 是否正确设置了 SameSite 属性
方案4(推荐): localStorage 代替 js-cookie
修改原项目中使用的 cookie 相关代码。直接弃用 js-cookie,使用 localStorage 来存储用户信息。
js
// import Cookies from 'js-cookie'
const TokenKey = 'saber-access-token'
const RefreshTokenKey = 'saber-refresh-token'
export function getToken() {
// return Cookies.get(TokenKey)
return localStorage.getItem(TokenKey)
}
export function setToken(token) {
// return Cookies.set(TokenKey, token)
return localStorage.setItem(TokenKey,token)
}
export function getRefreshToken() {
// return Cookies.get(RefreshTokenKey)
return localStorage.getItem(RefreshTokenKey)
}
export function setRefreshToken(token) {
// return Cookies.set(RefreshTokenKey, token)
return localStorage.setItem(RefreshTokenKey,token)
}
export function removeToken() {
// return Cookies.remove(TokenKey)
return localStorage.remove(TokenKey)
}
export function removeRefreshToken() {
// return Cookies.remove(RefreshTokenKey)
return localStorage.remove(RefreshTokenKey)
}
总结
我使用了方案4最简单的方式,直接弃用 js-cookie,改用 localStorage。重新将 b 项目打包部署后,在 a 项目的 iframe 标签内进行嵌套展示,发现登录成功后正常跳转到了 b 项目的首页,且功能正常。