• node 第十一天 纯使用第三方cookie 实现 跨源单点登录(或者说免登录) 以及白话说cookie的跟踪原理


    1. 白话说cookie跟踪, 广告推送基础原理
    第三方cookie
    首先第三方要在第一方设置cookie, 必要条件是两方互相同意
    天猫和淘宝是两个域名 天猫决定可以设置淘宝的cookie, 比如去请求一个淘宝的接口 [注释1] (这里反映了第一方的同意)
    访问者访问天猫, 同时设置上了一个淘宝的cookie ;设置第三方cookie本质上是后端设置的,因为js设置cookie是不能指定domain为第三方源的(这里需要后端设置cookie, 第三方设置cookie必须同时指定 SameSite=None; Secure)因为是第三方给第一方设置cookie 这里反映了第三方的同意
    例如 :
    1 登录天猫时 设置一个淘宝cookie user: li 这样就知道了你的用户名
    2 在天猫上买了一瓶水 又设置一个淘宝的cookie buy: water 这样就知道你渴了要买水
    然后你去登录淘宝, 带上已经有的淘宝域下cookie, 既然访问的是淘宝, 那当然可以带上淘宝自己的cookie
    淘宝就知道你的用户名, 你要买水, 给你推多多的水

    注释1: 请求第三方接口, 例如:

    • 可以通过ajax(需要处理跨域, 和cookie许可, 如果只是为了设上第三方cookie一般不这么做)
    • 请求一张第三方图片, 第三方做相应操作
    • 请求一个第三方js文件 (百度统计的做法)

    2.设想一个单点登录需求
    我有两个网站A(http://127.0.1.1:5500)和B(http://127.0.4.1:5500), 我的服务器是http://127.0.1.1:3007, , 用户只要登录B网站, 就不需要再登录A网站了(A或B网站的登录接口都是访问一个服务器), 这是一个单点登录的特例

    • 前置知识一, 对浏览器来说,cookie是区分域,不区分端口的,
    • 前置知识二, 标记为 secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。它永远不会使用不安全的HTTP 发送(本地主机除外) 本文所用的ip都是本地主机ip
    • 同一个ip地址下多个端口的cookie是共享的, 意味着我只要登录过B网站, 然后服务器端往B网站设置一个cookie,设置cookie的过程可以是, B请求服务器的登录接口, 然后服务器做相应操作, 显然这个cookie的域肯定是服务器的源127.0.1.1, 这样就成功在B上设置上了第三方cookie, 当我访问A时, A和服务器除了端口是同源的, 访问登录接口时会带上我在B设置的源为127.0.1.1的cookie, 携带了这个cookie我的服务器就让用户免登录.
    • 这么一来流程就通了~
    • 什么? 你想用户登录过A也可以免登录B, 换个思维, 你让A的登录接口请求服务器http://127.0.4.1不就可以了吗 ! ! !

    3.下面是演示代码 (代码处理跨域的思路在第九天已经说了, 代码也是第九天的改造)
    node.js 服务端代码

    const fs = require('fs');
    const url = require('url');
    const http = require('http');
    const querystring = require('querystring');
    const path = require('path');
    
    const server = http.createServer((req, res) => {
      if (['http://127.0.1.1:5500', 'http://127.0.4.1:5500'].includes(req.headers['origin'])) {
        res.setHeader('Access-Control-Allow-Origin', req.headers['origin']);
      }
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type, x-token');
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      res.setHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, OPTIONS, DELETE');
      res.setHeader('Access-Control-Max-Age', 1728000); //预请求缓存20天
      if (req.method === 'OPTIONS') {
        res.writeHead(200);
        res.end();
      } else {
        let cookie = req.headers.cookie;
        cookie = cookie?.replace(/\s/g, '');
        const cookieInfo = querystring.parse(cookie ?? '', ';');
        //有cookie直接免登录
        if (cookieInfo.token === '10086') {
          res.writeHead(200, {
            'content-type': 'application/json'
          });
          res.end(JSON.stringify({ code: 1, data: { name: 'jian' }, msg: '登录成功' }));
        } else if (req.url === '/login') {
          req.on('data', chunk => {
            let { pw } = JSON.parse(chunk.toString('utf-8'));
            if (pw === '123456') {
              let date = new Date();
              date.setDate(date.getDate() + 1);
              let expires = date.toUTCString();
              //这里设置第三方cookie
              res.writeHead(200, {
                'content-type': 'application/json',
                'set-cookie': [`token=10086; Expires=${expires}; SameSite=None; Secure`]
              });
              res.end(JSON.stringify({ code: 1, data: { name: 'jian' }, msg: '登录成功' }));
            } else {
              res.writeHead(200, {
                'content-type': 'application/json'
              });
              res.end(JSON.stringify({ code: 0, data: {}, msg: '密码错误' }));
            }
          });
        } else {
          res.writeHead(200, {
            'content-type': 'application/json'
          });
          res.end(JSON.stringify({ code: 0, data: {}, msg: '未登录' }));
        }
      }
    });
    server.listen(3007, '127.0.1.1');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    4.网站A和网站B代码, 运行在不同的源(origin)下

    DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>ajaxCookietitle>
      head>
      <body>
        <div id="login" style="display: none">
          <h1>请登录:h1>
          <input id="password" name="pw" type="password" />
          <input id="submit" type="submit" value="登录" />
        div>
        <div id="content" style="display: none">
          <h1>欢迎您~h1>
        div>
      body>
      
      <script>
        function myAjax({
          method = 'GET',
          url = '/',
          data = {},
          headers = {
            'content-type': 'application/x-www-form-urlencoded'
          },
          responseType = 'text',
          withCredentials = false,
          success = res => {
            console.log(res);
          },
          error = err => {
            console.log(err);
          }
        }) {
          let ajax = new XMLHttpRequest();
          ajax.withCredentials = withCredentials;
          ajax.open(method, url);
          for (const key in headers) {
            if (Object.hasOwnProperty.call(headers, key)) {
              ajax.setRequestHeader(key, headers[key]);
            }
          }
          ajax.responseType = responseType;
          ajax.send(data);
          ajax.onreadystatechange = () => {
            if (ajax.readyState === 4) {
              success(ajax.response);
            }
          };
          ajax.onerror = err => {
            error(err);
          };
        }
      script>
      <script>
        const loginEl = document.querySelector('#login');
        const submitEl = document.querySelector('#submit');
        const contentEl = document.querySelector('#content');
        //前端控制路由
        function toLogin() {
          loginEl.style.display = 'block';
          contentEl.style.display = 'none';
        }
        function toContent() {
          loginEl.style.display = 'none';
          contentEl.style.display = 'block';
        }
        //进入即发起请求(不携带密码, 携带同源cookie)
        myAjax({
          method: 'POST',
          url: 'http://127.0.1.1:3007/ajax',
          data: JSON.stringify({}),
          headers: {
            'content-type': 'application/json',
            'x-token': 'x-token'
          },
          responseType: 'json',
          withCredentials: true,
          success: res => {
            console.log(res);
            if (res.code === 1) {
              this.toContent();
            } else {
              this.toLogin();
            }
          },
          err: err => {
            console.log(err);
          }
        });
        submitEl.onclick = () => {
          let pw = document.querySelector('#password').value;
          myAjax({
            method: 'POST',
            url: 'http://127.0.1.1:3007/login',
            data: JSON.stringify({
              pw: pw
            }),
            headers: {
              'content-type': 'application/json',
              'x-token': 'x-token'
            },
            responseType: 'json',
            withCredentials: true,
            success: res => {
              console.log(res);
              if (res.code === 1) {
                this.toContent();
              } else {
                this.toLogin();
              }
            },
            err: err => {
              console.log(err);
            }
          });
        };
      script>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120

    5. 你在B登录后就能免登录A啦 fun:-)

  • 相关阅读:
    30岁被裁,我想明白的几件事....
    MATLAB 用语句新建和打开 Simulink 模型
    <类与对象>总结与扩展——《C++初阶》
    嵌入式C语言中整形溢出问题分析
    粒子群算法——王者荣耀的视野共享辅助决策的底层原理
    解决mysql语句MAX()函数中出现的问题
    K8s进阶之路-Pod的生命周期
    [Linux]文件描述符(万字详解)
    Socket网络编程——(一)
    .Net项目混淆/加密工具
  • 原文地址:https://blog.csdn.net/weixin_43546457/article/details/133961932