一、认识跨域

1、同源

符合”协议+域名+端口”三者相同,就是同源

2、同源策略

同源策略,其初衷是为了浏览器的安全性,通过以下三种限制,保证浏览器不易受到XSS、CSFR等攻击。

Cookie、LocalStorage 和 IndexDB 无法读取

DOM 和 Js对象无法获得

AJAX 请求不能发送

3、跨域

引入同源对概念,是因为我们常指对跨域,其实就是浏览器同源策略限制的一类请求场景。

4、常见跨域情景

5、跨域解决方案

通过jsonp跨域

document.domain + iframe跨域

location.hash + iframe

window.name + iframe跨域

postMessage跨域

跨域资源共享(CORS)

nginx代理跨域

nodejs中间件代理跨域

WebSocket协议跨域

注:我的笔记着重学习第 1 和第 6 种方法,其它第不做介绍。

6、*补充

这里不是重点,只要知道我们平时说对跨域是狭义对跨域,仅仅是浏览器出于安全考虑对一种限制,而广义对跨域,包含以下特征:

资源跳转: A链接、重定向、表单提交。

资源嵌入:

onBack({

"status": true, "user": "admin"})

在这里,返回内容调用了全局函数 onBack ,并且是带数据的调用。

3、Jquery 实现

$.ajax({

url: 'http://www.demo.com:8080/login',

type: 'get',

dataType: 'jsonp', // 请求方式为jsonp

jsonpCallback: "onBack", // 自定义回调函数名

data: {}

});

4、vue 实现

this.$http.jsonp('http://www.domain2.com:8080/login', {

params: {},

jsonp: 'onBack'

}).then((res) => { console.log(res); })

三、跨域资源共享(CORS)

好吧,我的笔记里不全面整理了,直接接入 阮一峰老师 的博客 跨域资源共享 CORS 详解——阮一峰

1.介绍

CORS 是基于 http1.1 的一种跨域解决方案,它的全称是 Cross-Origin Resource Sharing(跨域资源共享)。MDN参考

总体思路:如果浏览器要跨域访问服务器的资源,需要获得服务器的允许。

而一个请求可以附带很多信息,从而对服务器有不同的影响。有的只是获取数据,有的会修改数据。

CORS 针对不同的请求,规定了3种交互模式:

简单请求

需要预检的请求

附带身份凭证的请求

这3种模式层层递进,请求可以做的事越来越多,要求也越来越严格。

当浏览器发起请求(XMLHttpRequest 还是 fetch api)之前,都会先判定下属于哪一类请求模式

2.简单请求

判定当请求同时满足以下条件时,浏览器会认为它是一个简单请求:

head 请求,比如当浏览器下载文件时,可能不知道文件有多大,服务器如果立即响应读取文件会占用服务器资源。

但浏览器可能又不想下载了,所以可以先发送一个 head 请求,来让服务器告知这个文件的大小(通过响应头)。

当真正想下载时,再发送下载请求。

举例:

// 简单请求(默认 get 请求)

fetch("http://example.com/api/list");

// 简单请求

fetch("http://example.com/api/list", {

method: "POST"

})

// 非简单请求(请求方法不满足)

fetch("http://example.com/api/list", {

method: "PUT"

})

// 非简单请求(有额外的请求头)

fetch("http://example.com/api/list", {

headers: {

a: 1

}

})

// 非简单请求(请求头 content-type 不满足要求)

fetch("http://example.com/api/list", {

method: "POST",

headers: {

"content-type": "application/json"

}

})

交互规范 1.请求头中会自动加入 Origin 字段

2.服务器响应头中应包含 Access-Control-Allow-Origin 字段。

当服务器收到请求后,如果允许请求跨域访问,则需要在请求头中添加该字段。值可以是:

*,表示无限制。

具体的源,比如 http://localhost:5500

交互过程如下图示:

举例:在 http://localhost:5500/index.html 页面发送请求 fetch("http://localhost:3001/api/login", { method: "POST" }) :

3.非简单请求

这样的请求一般会对服务器有特殊的要求,常见的比如 PUT 或 DELETE 请求,或者 Content-Type: application/json。

交互规范 浏览器发送预检请求,询问服务器是否允许 预检请求响应,服务器允许 浏览器发送真实请求,服务器完成真实的响应比如对这个请求:Content-Type: application/json + 有自定义的请求头:

fetch("http://localhost:3001/api/login", {

method: "POST"

headers: {

"Content-Type": "application/json",

"a": "aa",

"b": "bb",

},

body: JSON.stringify({

name: "下雪天的夏风",

pwd: "123",

}),

})

3.1发送预检请求

特点:

请求方法是 OPTIONS 没有请求体 请求头中包含 Origin:请求的源,和简单请求的一样。 Access-Control-Request-Method:后续真实请求将使用的请求方法。 Access-Control-Request-Headers:后续真实请求会额外发送的头信息字段。

3.2预检请求响应

服务器收到预检请求后,如果允许这样的请求,需要在响应头中添加如下的字段(不需要响应任何请求体):

Access-Control-Allow-Origin:允许的源,和简单请求的一样。 Access-Control-Allow-Methods:表示允许的后续真实的请求方法。 Access-Control-Allow-Headers:表示允许改动的请求头。 Access-Control-Max-Age:告诉浏览器在多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求了,非必填。

预检请求的判断可以放到中间件中,也可以使用已有的中间件 koa-cors

3.3浏览器发送真实的请求,服务器完成真实的响应。

一旦通过预检请求,后续过程和简单请求一致。

整个过程:

代码实现:

客户端

服务端:

const Koa = require("koa");

const Router = require("koa-router");

const { bodyParser } = require("@koa/bodyparser");

const app = new Koa();

const router = new Router();

router.post("/api/login", (ctx) => {

ctx.set("Access-Control-Allow-Origin", "http://localhost:5500");

const { name, pwd } = ctx.request.body;

if (name === "下雪天的夏风" && pwd === "123") {

ctx.set("set-cookie", `token=aaa; domain=localhost; max-age=1000`);

ctx.body = {

code: 200,

msg: "登录成功",

};

} else {

ctx.body = {

code: 500,

msg: "用户名或密码错误",

};

}

});

router.options("/api/login", (ctx) => {

ctx.set("Access-Control-Allow-Origin", "http://localhost:5500");

ctx.set("Access-Control-Allow-Methods", "post");

ctx.set("Access-Control-Allow-Headers", "content-type,a,b");

ctx.set("Access-Control-Max-Age", "10");

ctx.body = "success";

});

app.use(bodyParser()).use(router.routes());

app.listen(3001);

预检请求:

真实的请求:

附带身份凭证注意到,真实请求会设置 cookie,但并没有设置成功:

这是因为默认情况下,跨域请求并不会设置 cookie,同时请求也不会附带 cookie。

而想要实现附带身份的请求(也就是附带 cookie),需要2点

浏览器配置附带 cookie

// xhr

const xhr = new XMLHttpRequest();

xhr.withCredentials = true;

// fetch api

fetch(url, {

credentials: "include" // 默认是 same-origin,同源才会附带。

})

服务器响应头中添加字段 Access-Control-Allow-Credentials: true,来明确告知客户端允许这样的凭据。

// koa

ctx.set("Access-Control-Allow-Credentials", "true");

注意:

如果是非简单请求,在预检请求和真实请求中都需要添加该响应头!否则还是会报跨域错误。对于附带身份的请求,服务器不得设置 Access-Control-Allow-Origin: * 。