代码之家  ›  专栏  ›  技术社区  ›  Allan of Sydney

在同一个域上的站点之间共享cookie-无头/分离的cms

  •  8
  • Allan of Sydney  · 技术社区  · 6 年前

    我挑战的背景

    我在建一个无头wordpress/woomerce商店。

    如果你不熟悉无头cms的概念,我会将商店的内容(产品、图片、文本)放到wordpress/woomerce rest api上。这样,当我在现代语言/库中进行开发时,我可以为我的客户提供CMS仪表板的好处,在我的例子中-react!

    如果可能的话,我想在wordpress/woomerce/php中保存结帐。根据我应用这个代码/样板文件的项目,我怀疑我将不得不修改和更改支付网关,使这个安全和PCI兼容将在php/wordpress中更容易-有一个完整的插件。

    这意味着整个商店/前端都将处于react状态,除了用户希望完成订单时将被重定向到cms前端(wordpress,php)的购物车。

    挑战

    这使得为会话管理cookie变得非常不直观和非常规。当用户从商店(react站点)重定向到签出(woomerce/php站点)时,购物车会话必须在两个站点之间保持。

    此外,对woomerce的请求通过我的react客户机所在的node/express服务器进行路由。我这样做是因为我想隐藏wordpress地址,这样我就可以应用graphql来清理我的请求和响应。 这个问题是,在这个过程中,cookie会丢失,因为我的客户端和我的cms是通过中间人(我的节点服务器)通信的——我需要额外的逻辑来手动管理cookie。

    Rough sketch of architecture of solution

    守则

    当我试图向购物车中添加某些内容时,从操作创建者(我使用redux进行状态管理)中,我在我的节点/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服务器上,我向woomerce提出请求:

    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);
        }
    });
    
    1 回复  |  直到 6 年前
        1
  •  2
  •   Allan of Sydney    6 年前

    根据@tarunlalwani提供的线索(万分感谢!)在他的评论中,我设法想出了一个解决办法。

    Cookie域设置

    因为我使用的是两个独立的站点,为了让这一点起作用,我必须确保它们都在同一个域上,并且该域设置在所有cookies中。这确保了cookies包含在节点/快速服务器之间的我的请求中(位于 somedomain.com )和Woocommerce CMS(坐在 wp.somedomain.com ,而不是对 wp.somedomain 子域。这是通过设置 define( 'COOKIE_DOMAIN', 'somedomain.com' ); 在我的 wp-config.php 在CMS上。

    手动获取和设置Cookie

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

    在反应中,我必须检查cookie是否存在,如果是这样的话,我必须在get请求的标题中把它发送给节点/ 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并将其保存到客户端( setCookieFunc )中。

    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客户端上与cookies交互的库。


    哥特恰

    如果您试图在开发环境中(使用localhost)实现此功能,则需要做一些额外的工作。见 Cookies on localhost with explicit domain