You need to enable JavaScript to run this app. 多域名场景下WordPress跨站复用的JWT安全存储最佳方法
优惠活动
大模型
产品
解决方案
定价
更多

多域名场景下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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.02 10:15:03