出于浏览器的同源策略限制会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域),在出现协议(protocol),主机(host)和端口号(port)有一种不一致时,就会出现跨域。在前后台交互中,ajax
请求的url不是同一个服务器下面的URI,就会出现跨域请求问题。由于安全性的问题,浏览器默认不支持跨域
调用.
同源策略仅限于浏览器 跨域请求是可以发出去的但是响应response被浏览器堵塞了
协议 域名 端口号 一致即是 同域
三者有一个不同就是跨域
http:// www. baidu.com : 8080 / index.html
协议 子域名 主域名 端口号 请求资源地址
当协议,子域名,主域名,端口号,中任意一个不相同时,都算作不同域
不同域之间的相互请求资源,就算作跨域
Javascript 出于安全方面的考虑,不允许跨域调用其他页面的对象。跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致
例如http://www.example.com/
http://api.example.com/detail.html | 不同源 | 域名不同 |
---|---|---|
https//www.example.com/detail.html | 不同源 | 协议不同 |
http://www.example.com:8080/detail.html | 不同源 | 端口不同 |
http://api.example.com:8080/detail.html | 不同源 | 域名、端口不同 |
https://api.example.com/detail.html | 不同源 | 协议、域名不同 |
https://www.example.com:8080/detail.html | 不同源 | 端口、协议不同 |
http://www.example.com/detail/index.html | 同源 | 只是目录不同 |
为什么浏览器不支持跨域
cookie localStorage IndexedDB 同源策略不支持跨域
DOM元素也有同源策略 iframe
ajax也不支持跨域
不存在跨域请求的限制
- script
- img
- link
实现跨域的方式
jsonp、cors、nginx、http-proxy、postMessage、doucment.domain、window.name、location.hash、websocket等
jsonp 只能处理get请求 不支持post put delete 不安全(xss攻击 )
前端通过script标签请求指定URL,服务器返回一个函数执行语句,函数名称由查询参callback的值决定,函数的参数为服务器返回的json数据,该函数在前端执行后即可获得数据 jsonp中cb每次都要不同
JSONP原理是通过<script>标签的src来帮助我们请求不同域的数据.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47<!-- index.html -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
function express() {
jsonp({
url: 'http://localhost:3000/say',
params: {wd:'a',json:1},
cb:'show'
}).then(data=>{
console.log(data)
})
}
express()
function jsonp({url,params,cb}) {
return new Promise((resolve,reject)=>{
let script = document.createElement('script')
window[cb]=function(data){
resolve(data)
document.body.removeChild(script)
}
params = {...params,cb}
let arrs = []
for(let key in params){
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
</script>
<!-- 服务端处理 -->
let express = require('express')
let app = express()
app.get('/say',function(req,res){
let {wd,cb}=req.query
console.log(wd)
res.end(`${cb}('44444')`)
})
app.listen(3000)
cors跨域资源共享
CORS(Cross Origin Resoure Share)跨域资源共享,后端方案解决跨域,需要服务器对请求进行检查并设置对响应头做相应处理,从而允许跨域请求
- 响应简单请求:method为’get/post/head’,没有自定义请求头,Content-Type是application/www-form/urlencoded,multipart/form-data或text/plain,通过添加响应头解决
res.setHeader('Access-Control-Allow-Origin','*')
或res.setHeader('Access-Control-Allow-Origin','http://xxx:xx')
允许任意网站请求数据或者允许指定的域名访问- 响应preflight请求,需要响应浏览器发出的options请求(预检请求),根据情况设置响应头
1
2
3
4
5
6
7
8
9
10
11 // 服务器 忽略options请求
if(req.method == 'options') {
res.writeHead(200, {
'Access-Control-Allow-Origin': 'http://localhost:3000',
'Access-Control-Allow-Headers':'x-Token',
'Access-Control-Allow-Methods': 'PUT'
})
res.end()
}
// 客户端请求
axios.get('http://localhost:3000/user', { headers: {'x-token': 'tew'} })
- 如果需要携带cookie信息,则请求变为credential请求
res.setHeader('Access-Control-Allow-Credentials',true) //允许携带cookie
客户端发送请求(ajax/fetch)
1
2
3
4
5
6
7
8
9
10
11
12
13
14// index.html 请求接口
var xhr = new XMLHttpRequest;
document.cookie = 'name=tew'
xhr.withCredentials = true
//xhr.open('GET','http://localhost:4000/say',true);
xhr.open('DELETE','http://localhost:4000/say',true);
xhr.setRequestHeader('name', 'tew')
xhr.onreadystatechange=function() {
if(xhr.readyState==4&&xhr.status==200){
console.log(xhr.response)
console.log(xhr.getResponseHeader('name'))
}
}
xhr.send()服务端设置相关的头信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38let express = require('express')
let app = express()
let whiteList = ['http://localhost:3000']
app.use(function(req,res,next){
let origin = req.headers.origin;
if(whiteList.includes(origin)) {
// 设置哪个域名可以访问
res.setHeader('Access-Control-Allow-Origin',origin)
// 设置哪个请求头可以访问
res.setHeader('Access-Control-Allow-Headers','name')
// 设置哪个请求方法可以访问
res.setHeader('Access-Control-Allow-Methods','DELETE')
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials',true)
// options存活的时间
res.setHeader('Access-Control-Max-Age',6000)
// 允许前端获取的响应头
res.setHeader('Access-Control-Expose-Headers','name')
// 忽略options请求
if(req.method =='options') {
res.end()
}
}
next()
})
app.delete('/say',(req,res)=>{
console.log(req.headers)
console.log(req.headers.name)
console.log(req.headers.cookie)
res.setHeader('name','yn')
res.end('66666')
})
app.get('/say',(req,res)=>{
console.log(req.headers)
console.log(req.headers.name)
res.end('66666')
})
app.listen(4000)
nginx
http-proxy 代理 实现跨域请求
请求同源服务器,通过该服务器转发请求至目标服务器,得到结果再转发给前端,前端开发中测试服务器的代理功能(例如webpack的proxy)就是采用的该方案, 但是最终发布上线时入股web应用和接口服务器不在一个域仍会跨域
webpack-dev-server
中的proxyTable
1
2
3
4
5
6
7
8
9
10
11 proxy: {
'/api': {
target: 'https://www.xxx.com',
changeOrigin: true,
pathRewrite: {
'/api': ''
}
}
}
// 接口访问 http://localohost/api/getNews?page=1&pageSize=10
// 经过代理后实际访问 https://www.xxx.com/getNews?page=1&pageSize=10
postMessage
HTML5提供跨文档通信API(Cross-docuemnt messaging),该API为window对象新增window.postMessage方法,允许跨窗口通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 <!-- 包含iframe的父窗口 -->
<html lang="en">
<body>
<iframe src="http://127.0.0.1:5500/b.html" frameborder="0" onload="load()" id="frame"></iframe>
<script>
function load(){
var frame = document.getElementById('frame')
// postMessage(信息内容,接收消息窗口的源(orgin:协议+域名+端口))
frame.contentWindow.postMessage('parentData','http://127.0.0.1:5500') // 发送信息
window.onmessage = function(e){ //接收iframe中发送的信息
console.log(e) // MessageEvent{isTrusted:true,data:'iframeData',origin:'http://127.0.0.1:5500',…}
console.log(e.data) // iframeData
}
}
</script>
</body>
</html>
<!-- iframe b.html-->
<html lang="en">
<body>
<div>iframe</div>
<script>
window.onmessage = function(e) { // 使用 window.onmessage 监听 postMessage发送的信心
console.log(e.data) // parentData
e.source.postMessage('iframeData',e.origin) // 向父窗口数据
}
</script>
</body>
</html>
websocket
document.domain
window.name
location.hash
Ajax跨域请求携带COOKIE
1 | // 原生ajax xhr.withCredentials = true; //支持跨域发送cookies |