同源策略

同源策略(SOP - Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也是最基本的安全功能,是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。

所谓同源是指协议 + 域名 + 端口三者相同。这三者有任一不同,就会出现跨域。

跨域标识: No ‘Access-Control-Allow-Origin’

image-20210114110823613

一旦出现跨域现象,则:

  • cookie localstorage indexDB 无法获取
  • DOM js 对象无法获取
  • ajax请求不能发送

跨域解决方案

JSONP

通常为了减轻web服务器的负载,我们将js、css、img等静态资源分离到另一台独立域名的服务器上,在html页面中在通过相应的标签从不同域名下加载静态资源,这被浏览器允许。基于此原理,可以通过动态创建script,再请求一个带参网址实现跨域通信。

只支持get请求,需要后端配合,一般不使用这种方法

后端Node

const express = require("express");
const fs = require("fs");
const app = express();
// 中间件方法 设置node_modules为静态资源目录
// src拼接 http://localhost:3000/node_modules/...
app.use(express.static("node_modules"));
app.get("/", (req, res) => {
  fs.readFile("./index.html", (err, data) => {
    if (err) {
      res.statusCode = 500;
      res.end("500 Interval Serval Error!");
    }
    res.statusCode = 200;
    res.setHeader("Content-Type", "text/html");
    res.end(data);
  });
});
app.get("/user", (req, res) => {
  console.log(req);
  // 从请求中获取回调函数
  const cb = req.query.cb;
  res.end(`${cb}(${JSON.stringify({ name: "uzi" })})`);
  // res.json({ name: "uzi" });
});
app.listen(3000);

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jsonp跨域解决方案</title>
</head>
<body>
    <h1>jsonp跨域解决方案</h1>
    <script src="/axios/dist/axios.js"></script>
    <script>
        // axios新版本已经不支持jsonp方法
        axios.jsonp = url => {
            return new Promise((resolve, reject)=> {

                window.jsonCallBack = function (result) {
                    resolve(result);
                }
                // 动态创建script脚本,通过src属性拼接url地址
                let JSONP = document.createElement('script');
                JSONP.type = 'text/javascript';
                JSONP.src = `${url}?cb=jsonCallBack`;
                document.querySelector('head').appendChild(JSONP);
                setTimeout(() => {
                    document.querySelector('head').removeChild(JSONP);
                }, 1000)
            })
        }
        // axios.get('http://localhost:3000/user')
        axios.jsonp('http://127.0.0.1:3000/user')
        .then(res => {
            console.log(res);
        })
        .catch(err => {
            console.log(err);
        })
    </script>
</body>
</html>

Cors跨域

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>cors跨域</title>
</head>
<body>
    <h1>cors跨域</h1>
    <script src="/axios/dist/axios.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.9.4/qs.js"></script>
    <script>
        axios.defaults.baseURL = 'http://127.0.0.1:3000'
                
        axios.interceptors.request.use(function (config) {
            // 将数据转换为username=uzi%password=123格式
           config.data = Qs.stringify(config.data);
           return config;
        }, function (error) {
            return Promise.reject(error)
        })
        
        axios.post('/login', {
            username: 'uzi',
            password: 123
        }, {
            // 表示跨域请求时需要使用凭证, 允许携带cookie
            withCredentials: true
        })
        .then(res => {
            console.log(res);
        })
        .catch(err => {
            console.log(err);
        })
    </script>
</body>
</html>

后端

const express = require("express");
const fs = require("fs");
const app = express();

// 设置允许跨域访问该服务
app.all("*", function (req, res, next) {
  res.header("Access-Control-Allow-Origin", "http://localhost:3000");
  res.header("Access-Control-Allow-Headers", "Content-Type");
  res.header("Access-Control-Allow-Methods", "*");
  res.header("Content-Type", "application/json;charset=utf-8");
  // 允许携带cookie
  res.header("Access-Control-Allow-Credentials", "true");
  next();
});

// 中间件方法 设置node_modules为静态资源目录
// src拼接 http://localhost:3000/node_modules/...
app.use(express.static("node_modules"));
app.get("/", (req, res) => {
  fs.readFile("./index.html", (err, data) => {
    if (err) {
      res.statusCode = 500;
      res.end("500 Interval Serval Error!");
    }
    res.statusCode = 200;
    res.setHeader("Content-Type", "text/html");
    res.end(data);
  });
});

// Contains key-value pairs of data submitted in the request body. By default, it is undefined, and is populated when you use body-parsing middleware such as express.json() or express.urlencoded().
// { username: 'uzi', password: 123 }
app.use(express.json()); // for parsing application/json
// username=uzi%password=123
app.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

app.post("/login", (req, res) => {
  // 获取post请求中请求体的数据
  console.log(req.body);
  res.json({ status: 0, message: "登录成功" });
});

app.listen(3000);

也可以使用插件完成Cors配置

https://expressjs.com/en/resources/middleware/cors.html

const cors = require('cors');
app.use(cors());

Nginx反向代理跨域

类似于Node中间件代理,需要搭建一个中转Nginx服务器,用于转发请求

使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改Nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。

// proxy服务器
server {
    listen	81;
    server_name	www.baidu.com;
    location / {
        proxy_pass	http://www.bbbb.com:8080;	#反向代理
        # ......
    }
}

Nodejs中间件代理跨域

需要引入 cnpm i http-proxy-middleware -S

https://github.com/chimurai/http-proxy-middleware

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>中间件代理跨域</title>
</head>
<body>
    <h1>中间件代理跨域</h1>
    <script src="/axios/dist/axios.js"></script>
    <script>
        axios.defaults.baseURL = 'http://127.0.0.1:8080'
        axios.get('/user')
        .then(res => {
            console.log(res);
        })
        .catch(err => {
            console.log(err);
        })
    </script>
</body>
</html>

后端

const express = require("express");
const fs = require("fs");
const app = express();
// 中间件方法 设置node_modules为静态资源目录
// src拼接 http://localhost:3000/node_modules/...
app.use(express.static("node_modules"));
app.get("/", (req, res) => {
  fs.readFile("./index.html", (err, data) => {
    if (err) {
      res.statusCode = 500;
      res.end("500 Interval Serval Error!");
    }
    res.statusCode = 200;
    res.setHeader("Content-Type", "text/html");
    res.end(data);
  });
});
app.get("/user", (req, res) => {
  res.json({ name: "uzi" });
});
app.listen(3000);

代理服务器

const express = require("express");
const app = express();
const { createProxyMiddleware } = require("http-proxy-middleware");

// 代理服务器操作
// 设置允许跨域访问该服务
app.all("*", function (req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Content-Type");
  res.header("Access-Control-Allow-Methods", "*");
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});

// http-proxy-middleware
// 中间件 将每个请求转发到指定服务器
app.use(
  "/",
  createProxyMiddleware({
    target: "http://localhost:3000",
    changeOrigin: true,
  })
);

app.listen(8080);

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

http与https 下一篇