认识
一、认识
iFrame
跨域登录认证 是一个常见的解决方案,特别是在 微前端架构、单点登录(SSO
) 和 跨域认证 场景中。它通过在一个 iframe
中加载认证页面,使得主应用与认证服务(通常是身份提供者,IdP
)分离,从而解决跨域认证、共享用户会话和无感刷新等问题。
在微前端架构或多个子应用部署在不同域(例如:app.example.com
和 portal.example.org
)的场景下,直接在前端实现跨域认证(例如通过 cookie
或 localStorage
存储认证信息)会遇到浏览器的同源策略限制。为了实现 跨域认证,iFrame
提供了一种在不同域之间共享认证信息的解决方案。
实现 iFrame
跨域登录时,关键的技术要素有以下几点:
-
SameSite cookie
:浏览器安全策略,控制跨域请求中是否允许发送cookies
。要确保跨域iFrame
可以访问父域的cookie
,通常需要设置SameSite=None
且Secure
。 -
postMessage
:浏览器提供的安全跨源通信机制,允许不同域的窗口(或iframe
)间安全地交换消息。常用于跨域情况下的通信。 -
window.name
:window.name
是一种跨域存储的技术,可以在iframe
和主页面间传递认证信息。它比cookies
和localStorage
更容易跨域共享。
二、工作流
假设我们有两个应用:SP1(app.example.com)
和 IdP(idp.example.com)
,并且它们位于不同的域下。
1. 用户访问 SP1(app.example.com)
: 此时 SP1
会检测用户是否已登录。如果没有登录,SP1
会通过 iframe
嵌入一个指向 IdP
的认证页面。
// 在SP1中创建一个iframe来加载IdP的登录页面
const iframe = document.createElement('iframe');
iframe.src = 'https://idp.example.com/login'; // 指向IdP的认证页面
iframe.style.display = 'none'; // 隐藏iframe
document.body.appendChild(iframe);
2. 用户在 iframe
中输入认证信息(如用户名和密码)
3. 认证成功后: 用户通过 iframe
登录页面输入用户名和密码,IdP
验证身份。如果认证成功,IdP
会设置相应的认证信息(如 JWT
)到 IdP
域的 cookie
中。这里使用 SameSite=None
和 Secure
来确保跨域情况下的认证信息能够被正确存储。
Set-Cookie: access_token=xxx; Domain=idp.example.com; Path=/; SameSite=None; Secure;
4. IdP
将认证结果通过 postMessage
发送给父窗口(SP1
): 一旦认证成功,IdP
会通过 postMessage
将认证信息发送到父窗口(即 SP1
)。这可以避免直接访问 SP1
的 DOM
和数据,增强了安全性。
// 在IdP的iframe中,认证完成后通过postMessage将数据发送到父窗口
window.parent.postMessage({ token: 'access_token_value' }, 'https://app.example.com');
6. 主应用 SP1
接收到认证信息后: SP1
在页面中监听来自 iframe
的消息,一旦收到消息(即 access_token
),就表示用户已认证。此时,SP1
可以在本域内存储认证信息(如通过 cookie``、localStorage
或 sessionStorage
),并更新用户状态。
// SP1监听来自iframe的postMessage
window.addEventListener('message', function (event) {
if (event.origin === 'https://idp.example.com') {
const { token } = event.data;
// 使用token更新本地会话
document.cookie = `access_token=${token}; path=/; SameSite=None; Secure`;
// 其他认证相关的操作
}
}, false);
7. 用户完成登录并跳转回 SP1
页面: 一旦用户登录成功,SP1
就可以通过存储的 token
访问受保护资源。当 token
过期时,SP1
可以使用刷新 token
(如果有)来自动刷新 access_token
,并通过 iframe
进行无感刷新。