多域名场景下WordPress跨站复用的JWT安全存储最佳方法
跨多友方域复用AAM JWT的实现方案
核心设计逻辑
首先要明确浏览器的硬限制:my-domain.com、friend1-domain.com、friend2-domain.com是三个完全独立的根域名,受同源策略约束,普通Cookie、localStorage默认跨根域完全隔离,就算把JWT存在friend1的浏览器存储里,friend2的页面也根本读不到,纯靠友站本地存储的方案从浏览器机制上就走不通。
所以整个方案以主域my-domain.com为唯一信任根,通过主域HttpOnly会话Cookie + 安全postMessage消息通道实现无感知的JWT分发,既满足多友站跨域复用的需求,又符合现代浏览器的安全规范,避免凭证泄露风险。
落地流程拆解
- 首次认证阶段:用户在friend1-domain.com的友站触发认证,友站的plugin-for-friends插件按照原有逻辑,携带用户凭证请求my-domain.com的
/wp-json/aam/v2/authenticate接口。接口验证凭证通过生成JWT返回给友站的同时,会在my-domain.com域名下种植一个短时效的、跨域可携带的第一方会话Cookie,作为后续跨站复用凭证的唯一信任依据。 - 跨站复用阶段:用户跳转到friend2-domain.com时,友站的插件不需要引导用户重新认证,只需自动加载一个嵌入主域的隐藏iframe,iframe携带主域的会话Cookie完成有效性校验,校验通过后会通过postMessage通道把临时有效的JWT传递给友站,友站拿到JWT后即可正常调用主站的wp-rest-api接口。
- 失效兜底:如果主域的会话Cookie已经过期(默认设置15天有效期),或者JWT本身已经过期,友站的逻辑会自动跳转回原认证流程,引导用户重新完成凭证校验。
关键安全配置要求
- 主域的共享会话Cookie必须配置三个核心属性:
Secure(仅HTTPS传输)、HttpOnly(禁止前端JS读取,规避XSS窃取)、SameSite=None(允许跨域iframe携带,否则现代浏览器会拦截跨域场景下的Cookie传递)。 - 所有跨域消息交互必须做两层来源校验:主域返回消息前,校验接收消息的父页面origin是否在预先配置的友站白名单内;友站接收JWT前,校验消息来源是否是可信的my-domain.com,拒绝所有非可信来源的消息。
- 下发给友站的JWT必须设置短有效期(建议2小时),支持主站侧随时吊销,避免JWT泄露后的长期滥用风险。
核心代码示例
主域(my-domain.com,基于AAM插件扩展)
// 注册供iframe内嵌的共享JWT分发接口 add_action('rest_api_init', function() { register_rest_route('aam/v2', '/shared-jwt', [ 'methods' => 'GET', 'callback' => function(\WP_REST_Request $request) { // 配置友站白名单 $allowed_origins = ['https://friend1-domain.com', 'https://friend2-domain.com']; $origin = $request->get_header('Origin'); // 拦截非白名单站点的嵌入请求 if (!in_array($origin, $allowed_origins)) { return new WP_Error('forbidden', '未授权的站点来源', ['status' => 403]); } // 校验主域的HttpOnly会话有效性 if (!isset($_COOKIE['aam_shared_session']) || !aam_validate_shared_session($_COOKIE['aam_shared_session'])) { return new WP_Error('no_session', '会话已失效', ['status' => 401]); } $user_id = aam_get_user_by_shared_session($_COOKIE['aam_shared_session']); // 生成2小时有效的临时JWT $jwt = aam_generate_user_jwt($user_id, 7200); // 输出内嵌的消息响应脚本 return sprintf(' <script> window.addEventListener("message", function(e) { const allowed_sites = %s; if (allowed_sites.includes(e.origin)) { e.source.postMessage({ type: "aam_jwt_ready", token: "%s" }, e.origin); } }); </script> ', json_encode($allowed_origins), esc_js($jwt)); }, 'permission_callback' => '__return_true' ]); }); // 原有认证接口成功后,植入跨域共享会话Cookie add_action('aam_core_authentication_success', function(int $user_id) { $session_id = wp_generate_uuid4(); // 将会话ID与用户关联存储,设置15天过期 aam_store_shared_session($session_id, $user_id, time() + 15 * DAY_IN_SECONDS); // 种植符合安全配置的跨域Cookie setcookie( 'aam_shared_session', $session_id, time() + 15 * DAY_IN_SECONDS, '/', 'my-domain.com', true, true ); });
友站侧(plugin-for-friends 通用前端逻辑)
let cachedJWT = null; // JWT有效期校验(简化示例) function checkJWTExpire(jwt) { const payload = JSON.parse(atob(jwt.split('.')[1])); return payload.exp * 1000 < Date.now(); } // 统一获取有效JWT的方法 async function fetchValidJWT() { // 本地缓存的JWT有效则直接返回 if (cachedJWT && !checkJWTExpire(cachedJWT)) return cachedJWT; return new Promise((resolve, reject) => { // 创建隐藏的主域iframe const sharedIframe = document.createElement('iframe'); sharedIframe.style.display = 'none'; sharedIframe.src = 'https://my-domain.com/wp-json/aam/v2/shared-jwt'; // 监听来自iframe的JWT消息 const messageListener = (e) => { // 严格校验消息来源为主域 if (e.origin !== 'https://my-domain.com') return; if (e.data.type === 'aam_jwt_ready') { cachedJWT = e.data.token; // 清理临时元素和监听 window.removeEventListener('message', messageListener); document.body.removeChild(sharedIframe); resolve(cachedJWT); } }; window.addEventListener('message', messageListener); // iframe加载完成后主动请求JWT sharedIframe.onload = () => { sharedIframe.contentWindow.postMessage( {type: 'request_jwt'}, 'https://my-domain.com' ); }; document.body.appendChild(sharedIframe); // 5秒超时,会话失效则走原认证流程 setTimeout(() => { if (!cachedJWT) { window.removeEventListener('message', messageListener); document.body.removeChild(sharedIframe); reject(new Error('会话失效')); } }, 5000); }); } // 调用主站REST API的统一封装 async function callMyDomainAPI(endpoint) { try { const jwt = await fetchValidJWT(); const response = await fetch(`https://my-domain.com/wp-json/wp/v2/${endpoint}`, { headers: { 'Authorization': `Bearer ${jwt}` } }); return response.json(); } catch (e) { // 兜底:会话失效时跳转到原认证接口 window.location.href = `https://my-domain.com/wp-json/aam/v2/authenticate?redirect=${encodeURIComponent(window.location.href)}`; } }
落地效果
用户在任意友站完成一次认证后,15天内跳转其他友站时,插件会自动通过隐藏iframe拉取有效JWT,全程无感知,不需要用户重复输入凭证。整个链路中JWT不会长期存储在友站的本地环境,主域的会话Cookie也不会被友站的前端代码读取,从机制上规避了常见的凭证泄露风险。
内容的提问来源于stack exchange,提问作者guteLaune17
相关产品推荐
相关产品推荐

