为了防止网站遭到恶意攻击,导致信息被窃取,所以浏览器设计了同源策略。
浏览器是很开放的,只要在地址栏里面输入网址或者点击某个链接就可以访问了。正是因为这种开放的形态,才需要对浏览器做出限制,保护用户的信息安全。因此同源策略就是用来保护信息安全的。
同源政策由 Netscape 公司引入浏览器。同源策略是一个安全策略。所谓的同源,指的是协议,域名,端口相同。
浏览器出于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。
JSONP
JSONP
实现跨域的原理浏览器的同源策略限制不允许跨域请求;但页面中的 script
、img
、iframe
标签是例外不受同源策略限制。
Jsonp
就是利用script
标签跨域特性进行请求,通过 标签指向一个需要访问的地址并提供一个回调函数来接收数据。
思路:客户端事先准备一个接收数据的全局函数,之后客户端解析 script 标签发出请求。服务端接受到请求之后,返回函数的调用。客户端接收数据,执行回调。
假设JSONP请求如下:
jsonp({
url: 'http://path/to/server/b',
params: {A: a, B: b},
success: function myCallback (response) {}
})
背后其实在进行:
拼接一个script标签,
,从而触发对指定地址的GET请求
服务器端对这个GET请求进行处理,并返回字符串 "myCallback('response value')"
,前端script加载完之后,其实就是在script中执行 myCallback('response value')
,就完成了跨域的请求,因此就是只能用GET。
JSONP 只能发 GET 请求,因为本质上 script 加载资源就是 GET
优点:兼容性好,可以解决主流浏览器的跨域数据访问的问题。
缺点:只能进行 GET
请求,具有局限性,不安全。
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
const jsonp = ({ url, params, callbackName }) => {
const generateUrl = () => {
let dataSrc = ''
for (let key in params) {
if (params.hasOwnProperty(key)) {
dataSrc += `${key}=${params[key]}&`
}
}
dataSrc += `callback=${callbackName}`
return `${url}?${dataSrc}`
}
return new Promise((resolve, reject) => {
const scriptEle = document.createElement('script')
scriptEle.src = generateUrl()
document.body.appendChild(scriptEle)
window[callbackName] = data => {
resolve(data)
document.removeChild(scriptEle)
}
})
}
发送 POST 请求,使用 iframe
const requestPost = ({url, data}) => {
// 首先创建一个用来发送数据的iframe.
const iframe = document.createElement('iframe')
iframe.name = 'iframePost'
iframe.style.display = 'none'
document.body.appendChild(iframe)
const form = document.createElement('form')
const node = document.createElement('input')
// 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
iframe.addEventListener('load', function () {
console.log('post success')
})
form.action = url
// 在指定的iframe中执行form
form.target = iframe.name
form.method = 'post'
for (let name in data) {
node.name = name
node.value = data[name].toString()
form.appendChild(node.cloneNode())
}
// 表单元素需要添加到主文档中.
form.style.display = 'none'
document.body.appendChild(form)
form.submit()
// 表单提交后,就可以删除这个表单,不影响下次的数据发送.
document.body.removeChild(form)
}
// 使用方式
requestPost({
url: 'http://localhost:9871/api/iframePost',
data: {
msg: 'helloIframePost'
}
})
CORS
跨域资源共享CORS
是 “跨域资源共享” (Cross-origin resource sharing ) 的缩写,跨域资源共享(CORS
)是一种机制,是 W3C
标准。它允许浏览器向跨源服务器,发出XMLHttpRequest
或Fetch
请求。并且整个CORS
通信过程都是浏览器自动完成的,不需要用户参与。解决 AJAX 只能同源通信的限制问题。
前提是,浏览器必须支持这个功能,并且服务端也必须同意这个跨域请求。
CORS通信与AJAX没有任何差别。只不过,浏览器会在请求中携带一些头信息,需要以此判断是否运行其跨域,然后在响应头中加入一些信息即可。
使用自定义 HTTP
头部让浏览器与服务器进行沟通,从而决定请求或响应是成功还是失败。
简单请求: 浏览器直接发出 CORS
请求。具体来说,在头信息之中,增加一个Origin
字段。
(1)请求方法是以下三种方法之一:
(2)HTTP的头信息不超出以下几种字段:
当浏览器发现发现的ajax请求是简单请求时,会在请求头中携带一个字段:Origin。Origin中会指出当前请求属于哪个域(协议+域名+端口)。服务会根据这个值决定是否允许其跨域。
如果服务器允许跨域,需要在返回的响应头中携带下面信息:
Access-Control-Allow-Origin: http://manage.leyou.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin:可接受的域,是一个具体域名或者*,代表任意
Access-Control-Allow-Credentials:是否允许携带cookie,默认情况下,cors不会携带cookie,除非这个值是true
注意:如果跨域请求要想操作cookie,需要满足3个条件:
OPTIONS
方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。服务器对 AJAX
跨域请求设置限制条件。不符合简单请求的条件,会被浏览器判定为特殊请求,,例如请求方式为PUT。预检请求
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
非简单请求会发出一次预检测请求,返回码是204,预检测通过才会真正发出请求,这才返回200。这里通过前端发请求的时候增加一个额外的headers来触发非简单请求。
一个“预检”请求的样板:
OPTIONS /cors HTTP/1.1
Origin: http://manage.leyou.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.leyou.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
与简单请求相比,除了Origin以外,多了两个头:
Access-Control-Request-Method:接下来会用到的请求方式,比如PUT
Access-Control-Request-Headers:会额外用到的头信息
预检请求的响应
服务的收到预检请求,如果许可跨域,会发出响应:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://manage.leyou.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 1728000
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
除了Access-Control-Allow-Origin和Access-Control-Allow-Credentials以外,这里又额外多出3个头:
Access-Control-Allow-Methods:允许访问的方式
Access-Control-Allow-Headers:允许携带的头
Access-Control-Max-Age:本次许可的有效时长,单位是秒,过期之前的ajax请求就无需再次进行预检了
如果浏览器得到上述响应,则认定为可以跨域,后续就跟简单请求的处理是一样的了。
Access-Control-Allow-origin
字段,若配置过域名,则返回 Access-Control-Allow-origin + 对应配置规则里的域名的方式
。Access-Control-Allow-origin
字段做匹配,若无该字段,说明不允许跨域,从而抛出一个错误;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器接受该响应;若不同源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。webpack
代理通过 webpack
中的 proxy
进行代理,从而解决跨域的问题。
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://www.baidu.com/',
pathRewrite: { '^/api': '' },
changeOrigin: true, // target是域名的话,需要这个参数,
secure: false, // 设置支持https协议的代理
},
'/api2': {
.....
}
}
}
};
webpack proxy
,就是 webpack
提供的解决跨域的方案。其基本行为是接受客户端发送的请求后转发给其他的服务器,目的是为了便于开发者在开发的模式下解决跨域的问题。要想实现代理必须要一个中间服务器, webpack
提供服务器的工具是 webpack-dev-server
,只适用于开发阶段。
http-proxy-middleware
中间件) proxy
工作原理上市利用 http-proxy-middleware
这个 http
代理中间件,实现请求转发给其他的服务器。如下:在开发阶段,本地地址是 Http://loaclhost:3000 , 该浏览器发送一个前缀带有 /api 标识的向拂去器请求数据,但是这个服务器只是将这个请求转发给另一台服务器:
const express = require('express');
const proxy = require('http-proxy-middleware');
const app = express();
app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
app.listen(3000);
// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
在开发阶段,webpack-dev-server
会自动启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost
的一个端口上的,而后端服务器又是运行在另一个地址上。所以在开发阶段中,由于浏览器的同源策略,当本地访问的时候就会出现跨域资源请求的问题,通过设置 webpack proxy
实现代理请求后,相当于浏览器和服务器之间添加了一个代理商。当本地发送请求的时候,中间服务器会接受这个情求,并将这个请求转发给目标服务器,目标服务器返回数据后,中间服务器又会将数据返回给浏览器,当中间服务器将数据返回给服务器的时候,它们两者是同源的,并不会存在跨域的问题。服务器和服务器之间是不会存在跨域资源的问题的。
参考文章:
https://juejin.cn/post/6844903976505344013
https://www.jianshu.com/p/98d4bc7565b2
https://segmentfault.com/a/1190000015597029
https://segmentfault.com/q/1010000009708151