跨域相关问题 是最常见的基础面试题之一
出现原因:由于同源策略限制,同源策略是浏览器最核心也是最基本的安全功能,Web是构建在同源策略基础之上的。同源策略会阻止一个域的JS脚本和另一个域的内容进行交互。同源(指在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。
什么是跨域:当一个请求的url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
非同源限制:
(1)无法读取非同源网页的Cookie、LocalStorage和IndexedDB
(2)无法接触非同源网页的DOM
(3)无法向非同源地址发送AJAX请求
跨域解决方法:
(1)设置document.domain解决无法读取非同源的Cookie问题
浏览器通过document.domain属性来检查两个页面是否同源,因此将两个页面设置相同的document.domain,就可以共享Cookie(仅限主域相同,子域不同的跨域应用场景)
(2)跨文档通信API:window.postMessage()
调用postMessage方法实现父窗口 http://test1.com向子窗口http://test2.com发消息(子窗口同样可以使用该方法发送消息给父窗口)
主要使用于以下场景:
// 父窗口打开一个子窗口
var openWindow = window.open('http://test2.com', 'text');
// 父窗口向子窗口发消息(第一个参数代表发送的内容,第二个参数代表接收消息窗口的url)
openWindow.postMessage('Hello World!', 'http://test2.com');
调用message事件,监听对方发送的消息
window.addEventListener('message', function(e) {
console.log(e.source); // e.source 发送消息的窗口
console.log(e.origin); // e.origin 消息发向的网址
console.log(e.data); // e.data 发送的消息
}, false);
(3)JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单实用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求
网页通过添加一个
// 原生实现
<script src="http://test.com/data.php?callback=doSomeThing"></script>
// 向服务器test.com发起请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
<script tyoe="text/javascript">
function doSomeThing(res) {
// 处理获得的数据
console.log(res.data);
}
</script>
// jQuery ajax
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonCallback: 'handleCallback', // 自定义回调函数名
data: {}
})
// Vue.js
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback',
}).then((res) => {
console.log(res);
});
(4)CORS
CORS是跨域资源分享(Cross-Origin Resource Sharing)的缩写。是W3C标准,属于跨源AJAX请求的根本解决方法。
(分为两种方式,是否携带cookie决定)
-== 普通跨域请求:只需要服务器设置Access-Control-Allow-Origin==
-== 带cookie跨域请求,前后端都需要进行设置==
前端设置,根据 xhr.withCredentials字段判断是否带有cookie
// 原生ajax
var xhr = new XMLHttpRequest(); // IE8/9需要用window.XDomainRequest做兼容
// 假设固定前端携带cookie发起请求
xhr.withCredentials = true;
xhr.open('post', 'http://www.music.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlopended');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status == 200) {
alert(xhr.responseText);
};
};
// jQuery ajax
$.ajax({
url: 'http://www.music.com:8080/login',
type: 'get',
data: {},
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true // 会让请求头中包含跨域的额外信息,但不会含cookie
})
// vue-resource
Vue.http.options.credentials = true;
// axios
axios.defaults.withCredentials = true
(5)服务器设置
服务器端对于CORS的支持,主要是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
// 允许跨域访问的域名,若有端口需要写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.music.com");
// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会发出提示
response.setHeader("Access-Control-Allow-Credentials", "true");
// 提示OPTIONS检验时,后端需要设置两个常用的自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Rquested-with");
var http = request('http');
var serve = http.createServer();
var qs = require('querystring'); // Node.js内置模块querystring
server.on('request', function(req, res) {
var postData = '';
// 数据块接受中
req.addLisener('data', function(chunk) {
postData += chunk;
})
// 数据接收完毕
req.addListener('end', function() {
postData = qs.parse(postData); // 将postData转为对象
// querystring.parse()方法返回的对象不是原型,会继承自JavaScript的Object。意味着典型的Object方法如obj.toString()、obj.hasOwnProperty()等没有被定义并且不起作用。解决方法是:先将对象进行JSON字符串转化(JSON.stringify()),然后再转化成对象(JSON.parse())
// 数据后台设置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie
'Access-Control-Allow-Origin': 'http://www.music.com', // 允许访问的域(协议+域名+端口)
'Set-Cookie': 'l=a123456;Path=/;Music=www.music.com;HttpOnly' // HttpOnly属性,无法通过js脚本获取cookie信息,XSS攻击中无法通过document对象直接获取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
console.log('Server is runing!');
(6)webpack本地代理
在webpack.config.js中利用WebpackDevServer配置本地代理,详情配置查看官方文档
https://webpack.docschina.org/configuration/dev-server/#root
(7)WebSocket
WebSocket是一种双向通信协议,实现浏览器与服务器的全双工通信,同时也是跨域的解决方案,在建立连接(需要借助HTTP协议)之后,WebSocket的服务器与客户端都能主动向对方发送或者接受数据,连接建立好了之后client和server之间的双向通信就与HTTP无关了
(8)Nginx反向代理
最简单的跨域的方式,实现原理类似与Node中间件代理,需要搭建一个中转Nginx服务器,用于转发请求。
只需配置nginx,并在相应文件中配置端口号