在同一域上的站点之间共享 cookie - Headless / Decoupled CMS

Posted

技术标签:

【中文标题】在同一域上的站点之间共享 cookie - Headless / Decoupled CMS【英文标题】:Sharing cookies between sites on the same domain - Headless / Decoupled CMS 【发布时间】:2018-12-20 18:56:03 【问题描述】:

我的挑战背景

我正在建立一个无头 WordPress / WooCommerce 商店。

如果您不熟悉无头 CMS 的概念,我会通过 WordPress/WooCommerce REST API 提取商店的内容(产品及其图像、文本)。通过这种方式,我可以为我的客户受益于 CMS 仪表板,同时我可以使用现代语言/库进行开发,就我而言 - React!

如果可能的话,我想在 WordPress/WooCommerce/php 中保留结帐。根据我应用此代码/样板的项目,我怀疑我将不得不削减和更改支付网关,并且在 PHP/WordPress 中使这种安全和 PCI 兼容会更容易 - 有很多插件可以用于此。

这意味着整个商店/前端都将存在于 React 中,但购物车除外,当用户希望完成订单时,他们将在购物车中被重定向到 CMS 前端(WordPress、PHP)。

挑战

这使得管理会话的 cookie 变得相当不直观和不正统。当用户从商店(React 站点)重定向到结帐(WooCommerce/PHP 站点)时,购物车会话必须在两个站点之间持续存在。

此外,对 WooCommerce 的请求通过我的 React 客户端所在的 Node/Express 服务器进行路由。我这样做是因为我想隐藏 WordPress 地址,因此我可以应用 GraphQL 来清理我的请求和响应。 这个问题是在这个过程中,cookie 丢失了,因为我的客户端和我的 CMS 通过中间人(我的节点服务器)进行通信 - 我需要额外的逻辑来手动管理我的 cookie。

守则

当我尝试从动作创建者(我使用 Redux 进行状态管理)向购物车添加东西时,我点击了我的 Node/Express 服务器上的 api 对应端点:

export const addToCart = (productId, quantity) => async (dispatch) => 
  dispatch(type: ADD_TO_CART);
  try 
    // Manually append cookies somewhere here
    const payload = await axios.get(`$ROOT_API/addtocart?productId=$productId&quantity=$quantity`, 
      withCredentials: true
    );
    dispatch(addToSuccess(payload));
   catch (error) 
    dispatch(addToCartFailure(error));
  
;

然后在 Node/Express 服务器上我向 WooCommerce 发出请求:

app.get('/api/addtocart', async (req, res) => 
    try 
      // Manually retrieve & append cookies somewhere here
      const productId = parseInt(req.query.productId);
      const quantity = parseInt(req.query.quantity);
      const response = await axios.post(`$WP_API/wc/v2/cart/add`, 
        product_id: productId,
        quantity
      );
      return res.json(response.data);
     catch (error) 
      // Handle error
      return res.json(error);
    
);

【问题讨论】:

您是否尝试在您的wp-config.php 中设置define( 'COOKIE_DOMAIN', 'somedomain.com' );,这将确保wordpress 在主域而不是子域中生成cookie,并且它们都将与任何额外的cookie 共享,所以没有代码像你现在这样的复杂性 感谢@TarunLalwani 的提示-我还没有尝试过,我会试一试!如果这能解决我所有的饼干问题,那就太棒了。我最初的犹豫是 A) WooCommerce 是否会遵守此选项,以及 B) 从 React 到 WordPress 的所有 REST API 调用都是在服务器端(节点, express),而不是客户端(react)。我想这会妨碍 axios(我的 http 客户端)自动将相关的浏览器 cookie 添加到我发送到 WordPress 的请求中 A) 可以消除 - 我发现开发人员确认:“WooCommerce 使用与 WordPress 相同的 cookie 设置(例如 WordPress 登录 cookie)” 对于 B,您需要将您的请求中的所有浏览器 cookie 发送到 WP,并为返回 cookie 做同样的事情 你是对的,我必须在访问 CMS(WooCommerce via REST)之前将我的 cookie 从客户端(React)传递到服务器(Node),然后以相反的方式设置来自节点服务器内的客户端的 cookie。这很复杂,但它几乎就在那里! 【参考方案1】:

借助@TarunLalwani 在他的 cmets 中提供的线索(感谢一百万!),我设法制定了解决方案。

Cookie 域设置

由于我在使用两个单独的站点,为了使这个工作能够正常工作,我必须确保它们都在同一个域中,并且在所有 cookie 中都设置了域。这确保了 cookie 包含在我在 Node / Express 服务器(例如,somedomain.com)和 WooCommerce CMS(例如,wp.somedomain.com)之间的请求中,而不是 wp.somedomain 子域独有的。这是通过在我的 CMS 上的 wp-config.php 中设置 define( 'COOKIE_DOMAIN', 'somedomain.com' ); 来实现的。

手动获取和设置 Cookies

我的代码需要大量额外的逻辑,以便在请求通过客户端通过我的 Node / Express 服务器路由时包含 cookie。

在 React 中,我必须检查 cookie 是否存在,如果存在,我必须在 GET 请求的标头中将其发送到 Node / Express 服务器。

import Cookies from 'js-cookie';

export const getSessionData = () => 
  // WooCommerce session cookies are appended with a random hash.
  // Here I am tracking down the key of the session cookie.
  const cookies = Cookies.get();
  if (cookies) 
    const cookieKeys = Object.keys(cookies);
    for (const key of cookieKeys) 
      if (key.includes('wp_woocommerce_session_')) 
        return `$key=$Cookies.get(key);`;
      
    
  
  return false;
;

export const addToCart = (productId, quantity) => async (dispatch) => 
  dispatch(type: ADD_TO_CART);
  const sessionData = getSessionData();
  const config = ;
  if (sessionData) config['session-data'] = sessionData;
  console.log('config', config);
  try 
    const payload = await axios.get(`$ROOT_API/addtocart?productId=$productId&quantity=$quantity`, 
      withCredentials: true,
      headers: config
    );
    dispatch(addToSuccess(payload));
   catch (error) 
    dispatch(addToCartFailure(error));
  
;

在 Node / Express 服务器上,我必须检查我是否包含来自客户端的 cookie(保存在 req.headers 中,密钥为 session-data - 在此处使用 Cookie 作为密钥是非法的),并且如果我这样做了,请将其附加到我的 CMS 请求的标题中。

如果我没有找到附加的 cookie,这意味着这是会话中的第一个请求,所以我必须从我从 CMS 返回的响应中手动获取 cookie 并将其保存到客户端 (@987654332 @)。

app.get('/api/addtocart', async (req, res) => 
  try 
    const productId = parseInt(req.query.productId);
    const quantity = parseInt(req.query.quantity);
    const sessionData = req.headers['session-data'];
    const headers = ;
    if (sessionData) headers.Cookie = sessionData;
    const response = await axios.post(`$WP_API/wc/v2/cart/add`, 
      product_id: productId,
      quantity
    ,  headers );
    if (!sessionData) 
      const cookies = response.headers['set-cookie'];
      const setCookieFunc = (cookie) => 
        const [cookieKeyValue, ...cookieOptionsArr] = cookie.split('; ');
        const cookieKey = cookieKeyValue.split('=')[0];
        const cookieValue = decodeURIComponent(cookieKeyValue.split('=')[1]);
        const cookieOptions =  ;
        cookieOptionsArr.forEach(option => (cookieOptions[option.split('=')[0]] = option.split('=')[1]));
        if (cookieOptions.expires) 
          const expires = new Date(cookieOptions.expires);
          cookieOptions.expires = expires;
        
        res.cookie(cookieKey, cookieValue, cookieOptions);
      ;
      cookies.map(cookie => setCookieFunc(cookie));
    
    return res.json(response.data);
   catch (error) 
    // Handle error
    return res.json(error);
  
);

我不确定这是否是解决问题的最优雅的方法,但它对我有用。


备注

我使用 js-cookie 库与我的 React 客户端上的 cookie 进行交互。


陷阱

如果您尝试在您的开发环境(使用本地主机)中完成这项工作,则需要完成一些额外的工作。见Cookies on localhost with explicit domain

【讨论】:

以上是关于在同一域上的站点之间共享 cookie - Headless / Decoupled CMS的主要内容,如果未能解决你的问题,请参考以下文章

同一域上的多个 Laravel 5 项目

同一域上的不同页面显示不同的 cookie(按目标域)

如果 cookie 设置为同一域上的所有子域,则删​​除 cookie 不起作用

在不安全的域上允许 cookie

同一站点的两个单独域上的重定向问题

在同一域上的不同窗口之间进行通信[重复]