SSO项目中iframe内Tomcat托管应用LocalStorage为空的排查与解决
项目背景
我正在开发基于两个React应用的单点登录(SSO)项目:
- 登录React应用:部署在Tomcat服务器,登录后将会话信息存入自身LocalStorage,并向父页面发送消息。
- 本地React应用:本地开发环境运行在
localhost:3000,通过iframe嵌入上述Tomcat托管的登录应用。
本地应用的iframe代码:
<iframe id="sso-iframe" src={`${process.env.REACT_APP_SSO_DOMAIN}/sso/#/sso`} style={{ height: 1000, width: 1000 }} title="SSO Sync" ></iframe>
已执行操作
- 单独登录Tomcat上的登录应用,确认其LocalStorage已正确存储会话和用户信息。
- 回到本地应用页面,查看浏览器开发者工具,能看到两个域名的LocalStorage条目,但Tomcat应用对应的LocalStorage为空。
已尝试的CORS配置
为Tomcat配置了CORS过滤器但未生效,配置内容如下:
<filter> <filter-name>CorsFilter</filter-name> <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> <init-param> <param-name>cors.allowed.origins</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>cors.allowed.methods</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>cors.allowed.headers</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>cors.preflight.maxage</param-name> <param-value>3600</param-value> </init-param> </filter> <filter-mapping> <filter-name>CorsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
观察到的现象
- 当两个应用分别运行在
localhost:3000和localhost:3001时,iframe相关的LocalStorage能正常同步。 - 只要能确保Tomcat应用的LocalStorage状态正确,后续SSO流程即可正常运行。
问题
Tomcat托管的登录应用在iframe中LocalStorage为空的原因是什么?该如何解决?
解决方案
1. 核心原因:浏览器同源策略限制
LocalStorage是同源绑定的,只有协议、域名、端口完全一致的页面才能共享或直接访问彼此的LocalStorage。当本地应用localhost:3000嵌入Tomcat部署的应用(假设域名是xxx.com:8080)时,两者属于不同源,父页面的开发者工具中看到Tomcat应用的LocalStorage为空,是浏览器的安全限制——它不允许跨源页面直接读取其他域的LocalStorage,而非iframe内部的LocalStorage真的为空。
你在localhost:3000和localhost:3001能正常工作,是因为测试时可能通过postMessage完成了数据同步,而非直接读取跨域LocalStorage;且浏览器会按域名+端口区分存储上下文,但跨域场景下的限制依然存在。
2. 正确的SSO数据同步方案
放弃直接读取跨域iframe的LocalStorage,改用postMessage跨域通信实现数据同步:
- 登录应用在登录成功、写入LocalStorage后,主动向父页面发送会话信息:
// 登录应用的登录成功回调逻辑 const sessionData = localStorage.getItem('sessionInfo'); // 指定父页面域名,避免恶意消息 window.parent.postMessage( { type: 'SSO_LOGIN_SUCCESS', data: sessionData }, process.env.REACT_APP_PARENT_DOMAIN ); - 本地应用监听
message事件,接收并存储会话信息:// 本地应用的组件挂载钩子中 useEffect(() => { const handleSSOMessage = (event) => { // 验证发送方域名,防止非法请求 if (event.origin !== process.env.REACT_APP_SSO_DOMAIN) return; if (event.data.type === 'SSO_LOGIN_SUCCESS') { localStorage.setItem('ssoSession', event.data.data); // 执行后续登录逻辑 } }; window.addEventListener('message', handleSSOMessage); return () => window.removeEventListener('message', handleSSOMessage); }, []);
3. Tomcat CORS配置的补充调整
如果登录应用的接口请求存在跨域问题,当前CORS配置需要补充允许携带凭证(SSO通常依赖Cookie或授权头):
<init-param> <param-name>cors.support.credentials</param-name> <param-value>true</param-value> </init-param>
同时,将cors.allowed.origins从*改为具体的父页面域名(如http://localhost:3000)——当support.credentials设为true时,*是不被浏览器允许的。
4. 验证iframe内部的LocalStorage状态
要确认iframe中的登录应用是否真的写入了LocalStorage,不要通过父页面的开发者工具查看,而是直接在iframe上下文打开调试工具:右键点击iframe区域 → 选择“检查”,此时打开的工具属于iframe页面,可直接查看其LocalStorage的真实状态。
内容的提问来源于stack exchange,提问作者SoftwareNewbee

