跳到主要内容

认识

2025年01月08日
柏拉文
越努力,越幸运

一、认识


iFrame 跨域登录认证 是一个常见的解决方案,特别是在 微前端架构单点登录(SSO跨域认证 场景中。它通过在一个 iframe 中加载认证页面,使得主应用与认证服务(通常是身份提供者,IdP)分离,从而解决跨域认证、共享用户会话和无感刷新等问题。

在微前端架构或多个子应用部署在不同域(例如:app.example.comportal.example.org)的场景下,直接在前端实现跨域认证(例如通过 cookielocalStorage 存储认证信息)会遇到浏览器的同源策略限制。为了实现 跨域认证,iFrame 提供了一种在不同域之间共享认证信息的解决方案。

实现 iFrame 跨域登录时,关键的技术要素有以下几点:

  • SameSite cookie:浏览器安全策略,控制跨域请求中是否允许发送 cookies。要确保跨域 iFrame 可以访问父域的 cookie,通常需要设置 SameSite=NoneSecure

  • postMessage:浏览器提供的安全跨源通信机制,允许不同域的窗口(或 iframe)间安全地交换消息。常用于跨域情况下的通信。

  • window.namewindow.name 是一种跨域存储的技术,可以在 iframe 和主页面间传递认证信息。它比 cookieslocalStorage 更容易跨域共享。

二、工作流


假设我们有两个应用: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=NoneSecure 来确保跨域情况下的认证信息能够被正确存储。

Set-Cookie: access_token=xxx; Domain=idp.example.com; Path=/; SameSite=None; Secure;

4. IdP 将认证结果通过 postMessage 发送给父窗口(SP1: 一旦认证成功,IdP 会通过 postMessage 将认证信息发送到父窗口(即 SP1)。这可以避免直接访问 SP1DOM 和数据,增强了安全性。

// 在IdP的iframe中,认证完成后通过postMessage将数据发送到父窗口
window.parent.postMessage({ token: 'access_token_value' }, 'https://app.example.com');

6. 主应用 SP1 接收到认证信息后: SP1 在页面中监听来自 iframe 的消息,一旦收到消息(即 access_token),就表示用户已认证。此时,SP1 可以在本域内存储认证信息(如通过 cookie``、localStoragesessionStorage),并更新用户状态。

// 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 进行无感刷新。