同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。--来源 MDN

简单请求

浏览器禁止跨域分为两类:

  • 简单请求,在跨域请求response的时候禁止用户查看response,大部分请求都是简单请求。
  • 复杂请求,在跨域请求发出的时候禁止发送请求。

简单请求跨域的特点:

  • 跨域是浏览器的安全策略,如果使用爬虫直接请求并没有跨域问题。
  • 跨域是浏览器禁止用户查看跨域请求的response,而不是禁止发出跨域请求。
  • 跨域请求能够走到后端,后端处理完成之后前端访问不了(被浏览器block了)。

简单请求不会触发CORS预检请求。请注意,该术语并不属于Fetch(其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“简单请求”:

  • method为GET/HEAD/POST之一
  • 没有设置以下集合外的请求头:Accept、Accept-Language 、Content-Language 、Content-Type(需要注意额外的限制) 、DPR 、Downlink 、Save-Data 、Viewport-Width 、Width
  • Content-Type的值仅限于下列三者之一:(例如 application/json 为非简单请求)、text/plain 、multipart/form-data 、application/x-www-form-urlencoded
  • 请求中的任意XMLHttpRequestUpload对象均没有注册任何事件监听器;XMLHttpRequestUpload对象可以使用XMLHttpRequest.upload属性访问。
  • 请求中没有使用ReadableStream对象。

除了简单请求,别的都是复杂请求。

解决方案一:后端在response里面添加header

app.use(async (ctx, next) => {
  ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);
  ctx.set("Access-Control-Allow-Credentials", true);
  ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");
  ctx.set(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept, cc"
  );
  if (ctx.method === "OPTIONS") {
    ctx.status = 204;
    return;
  }
  await next();
});

解决方案二:nginx代理

解决方案三:改webpack配置

解决方案四:jsonp

JSONP(只支持get,用一个动态script标签直接进行get请求,因为同源策略对script无效)

手写jsonp
/**
* jsonp获取请求数据
* @param {string}url
* @param {object}params
* @param {function}callback
*/
function jsonp({ url, params, callback }) {
    return new Promise((resolve, reject) => {
        let script = document.createElement('script');
        params = JSON.parse(JSON.stringify(params));
        let arrs = [];
        for (let key in params) {
            arrs.push(`${key}=${params[key]}`);
        }
        arrs.push(`callback=${callback}`);
        script.src = `${url}?${arrs.join('&')}`;
        document.body.appendChild(script);
        window[callback] = function (data) {
            resolve(data);
            document.body.removeChild(script);
        }
    })
}

其它方案

  • document.domain + iframe (只有在主域相同的时候才能使用该方法)
  • window.name + iframe (name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。)
  • web sockets(web sockets是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。)