跨域
跨域是浏览器为了保证服务数据的安全性,而存在的一种限制请求的一种机制,只存在于浏览器。
主要工作原理就是根据有没有遵循同源策略(当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域),来判断一个请求是否跨域,然后决定是否允许这个请求。
同源策略
概念
- 1.同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
 - 2.同源指,发请求和接受请求的两端处在:同一协议,同一域名,同一端口下。
作用
 - 同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。
 
同源策略会限制的内容
- Cookie、LocalStorage、IndexedDB 等存储性内容
 - DOM 节点
 - AJAX 请求发送后,结果被浏览器拦截了
 
同源策略下有几个允许跨域加载资源的方式:
- <img src=’***’>
 - <link href=’***’>
 - <script src=’***‘>
 
跨域限制
跨域不仅对请求有限制,对请求的方法和发送的内容格式,也会有限制;除过默认允许的一些外,其他的非默认请求方法和内容格式都需要在服务端进行设置允许这些对应的可以访问。
因为非默认的请求方法和内容类型会在正式发送该请求前,发送一个预请求,来判断服务端是否允许该请求的访问,之后再发送该请求,如果服务端允许就正常获取响应内容,不允许就会报错,如下图。

服务端允许后允许访问,可以看到会有2个请求,第一就是预请求,第二个是正式发送的请求,预请求的方法是会显示OPTIONS。

设置代码(以node为例)
const koa = require("koa");
const app = new koa();
app.use(async ctx => {
  console.log(ctx.request.url);
  ctx.response.status = 200;
  // 设置允许跨域
  ctx.response.set({
    // 允许跨域访问的域名
    "Access-Control-Allow-Origin": "*",
    // 当请求方法是跨域默认请求之外的请求时,服务器需要设置对应的允许的请求方法
    "Access-Control-Allow-Methods": "PUT",
    // 允许该请求在第一次请求成功后的1000秒内,不需要再发送预请求
    "Access-Control-Max-Age": "1000",
  });
  ctx.response.body = "hi";
});
app.listen(3000, () => {
  console.log("ok");
});
列举一下跨域默认不需要预请求就可以发送的方法(Mthods)和内容类型(Content-Type)
跨域默认允许的请求方法(Method)
- GET
 - POST
 - HEAD
 
默认允许的发送请求内容类型(Content-Type)
- text/plain
 - multipart/form-data
 - application/x-www-form-urlencoded
 
默认允许的请求头
- Accept
 - Accept-Language
 - Content-Language
 - Content-Type
 
常见跨域场景

浪里行舟大佬的一些说明
第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。
第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。
这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
解决方案
JSONP
1.利用<script src=’***‘>可以跨域请求资源的原理2.这种方式只能通过GET请求。3.但是适配较广
代码
- 前端
 
...
<body>
  <button id="btn">请求</button>
  <script>
    let btn = document.querySelector("#btn");
    btn.onclick = function () {
      // 创建一个script标签块
      let frame = document.createElement("script");
      // 设置src链接
      // http://localhost:3000/person为请求借口
      // ? 分隔符
      // callback=func传给后端的一个回调函数
      frame.src = "http://localhost:3000/person?callback=func";
      // 添加到body内
      document.querySelector("body").append(frame);
    };
    function func(res) {
      // 后端成功收到请求后 会返回并执行 该函数
      console.log(res); // 打印收到的数据
    }
  </script>
</body>
...
- 后端(koa)
 
...
let data=require('data.json');
route.get('/person', async (ctx) => {
    newData = JSON.stringify(data);
    // 执行前端传递来的函数 并把数据作为参数传递
    ctx.body = `func(${newData})`;
})
...
- 以上前端代码可以通过jQuery来写,jQuery有封装好的方法直接使用
 
CROS
什么是CROS
- 跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。---来自MDN。
 
使用:
- 通过在后端设置相应头信息来允许一些指定的域名可以访问。
代码
 - 前端
 
<button id="btn">send</button>
<script>
  let btn = document.querySelector("#btn");
  btn.onclick = function () {
    getData();
  };
  let getData = () => {
    fetch("http://localhost:3000/person", {
      method: "GET",
    })
      .then(res => res.json())
      .then(data => {
        console.log(data);
      })
      .catch(err => {
        console.log(err);
      });
  };
</script>
- 后端(koa)
 
const cors = require("koa2-cors");
// 使用cors中间件
app.use(
  cors({
    // 允许get方法跨域请求
    allowMethods: ["GET"],
    // 在koa中不适用中间件 可以使用上下文对象来自己设置
    // ctx.set(...);
  })
);
Node/nginx做代理
因为服务器之间不用遵守同源策略,所以可以通过node或者nginx来转发请求,实现跨域
步骤
- 接受客户端请求 。
 - 将请求 转发给服务器。
 - 拿到服务器 响应 数据。
 - 将 响应 转发给客户端。
