什么是跨域
浏览器对于JavaScript的同源策略的限制,当前域名的JavaScript只能读取同域名下的窗口属性。
因为浏览器出于安全考虑,有同源策略。如果协议、域名或者端口有一个不同就是跨域,Ajax请求会失败。
出于什么安全考虑才会引入这种机制呢
其实主要是用来防止CSRF(Cross-site require forgery 跨站请求伪造)攻击的。简单点说,CSRF攻击是利用用户的登录状态发起恶意请求。
没有同源策略的情况下,A网站可以被任意其他来源的Ajax访问到内容。如果你当前A网站还存在登录状态,那么对方就可以通过Ajax获得你的任何信息。当然跨域并不能完全阻止CSRF。
跨域的请求发出去了没有
请求必然是发出去了,但是浏览器拦截了响应。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么Ajax就不会。因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止CSRF,因为请求毕竟是发出去了。
常见解决跨域问题的方式
JSONP
我们在调用js文件的时候不受跨域的影响,比如引入JQuery框架,或者引入图片等,也就是说拥有src
属性的标签都可以跨域,比如<script><img><iframe>
jsonp 的原理很简单,就是利用了<script>标签没有跨域限制的漏洞。通过<script>标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data){
console.log(data)
}
</script>
JSONP 使用简单且兼容性不错,但是只限于 get 请求。
在开发中可能会遇到多个JSONP请求的回调函数名是相同的,这时候就需要自己封装一个JSONP
function jsonp(url, jsonpCallback, success){
let script = document.createElement('script')
script.src = url
script.async = true
script.type = 'text/javascript'
window[jsonpCallback] = function(data){
success && success(data)
}
document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value){
console.log(value)
})
CORS
CORS 需要浏览器和后端同时支持。IE8 和 9需要通过XDomainRequest 来实现。
浏览器会自动进行CORS通信,实现CORS通信的关键是后端。只要后端实现了CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置CORS和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求
以Ajax为例,当同时满足以下两大条件,会触发简单请求
- 使用下列方法之一:
- GET
- HEAD
- POST
- Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlcoded
请求中的任意 XMLHTTPRequestUpload 对象均没有注册任何事件监听器;XMLHTTPRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
复杂请求
那么很显然,不符合以上条件的请求就肯定是复杂请求了。
对于复杂请求来说,首先会发起一个预检请求,该请求是 option 方法的,通过该请求来知道服务器端是否允许跨域请求。
document.domain
该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。
只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域
postMessage
这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接受消息
// 发送消息端
window.parent.postMessage('message', 'http://test.com')
// 接受消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
var origin = event.origin || event.originalEvent.origin
if (origin === 'http://test.com'){
console.log('通过')
}
})
网友评论