前端跨域问题
前言
说实在的,跨域在我做 Android 的时候压根没碰过…也没想过前端还这样,但是这个跨域问题是前端无法忽视的一个地方,一般当然职务作品会有后端配合,但是自己如果用第三方 API 做点什么了都解决不了跨域就不太好了。
什么是跨域
跨域的诞生是为了避免同源策略,在浏览器最基本的安全问题就是同源策略。
什么是同源
同源简单的来讲就是除了统一域名下之外,都是跨域,举个 🌰,http://www.foo.com/ 这个网址所能请求的,只有这个域名下的地址,其他无论不同端口,协议(包括 HTTP 和 HTTPS 差异),域名还是对应 IP,还是说没有 http://foo.com/ 这种二级域名不一致,都不能算同源。
例外
img
,link
和script
是允许跨域加载资源的。
触发跨域会怎样
其实在服务端看来,即便触发了跨域,在他看来也是正常的一个请求,只是,浏览器拦截了,浏览器认为这是不安全的,所以给前端报错。
解决方案
JSONP
利用script
不受同源策略影响所诞生的做法,用这种方式去加载一段执行代码,从而获取数据。
特性
- 兼容性好
- 只支持 GET 方法
- 有可能遭受 XSS 攻击
- 需要服务端配合!
实现
既然是利用script
标签,本来地址如何,改成放到script
就好了
1 | // http://www.bar.com/jsonp |
上述代码之后会返回什么呢?其实本质上服务端返回会从‘bar’
到jsonpCallback('bar')
的形式,实际上就是加载并调用了jsonpCallback
jQuery
1 | $.ajax({ |
jQuery 也自带 JSONP 请求方法
CORS
这种方式也是需要后端支持,其实解决跨域基本上都需要后端支撑,不然就太危险了不是么?
这种解决方案主要是设置后端的Access-Control-Allow-Origin
,Access-Control-Allow-Headers
,Access-Control-Allow-Methods
,Access-Control-Allow-Credentials
,Access-Control-Max-Age
,Access-Control-Expose-Headers
等响应头,来和浏览器交互,告诉浏览器不需要拦截的条件。
访问场景
CORS 请求有点不一样,它分为两种,一种是简单请求,一种是复杂请求,如果是复杂请求,则会发送两次请求,第一次会先用options
方法发起一个预检的请求到服务器,来获取是否能实际请求,避免出现服务器已经执行但是浏览器拦截的 CSRF 攻击。
简单请求
如果满足下述条件,则可以视为简单请求,会直接进行请求
- 使用下列方法之一:
- GET
- HEAD
- POST
- Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。该集合为:
- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencodeds
- 请求中的任意
XMLHttpRequestUpload
对象均没有注册任何事件监听器;XMLHttpRequestUpload
对象可以使用XMLHttpRequest.upload
属性访问。 - 请求中没有使用
ReadableStream
对象。
复杂请求
如果超出了简单请求的范围,就是复杂请求了,首先会进行一次options
方法检查,这个需要后端的同学明白。
代理
这种方式非常常用,开发的过程中用中间人代理,而且都是不用入侵代码的做法,十分方便。而且现在有一种做法就是前端写 node.js 和服务端渲染,所有请求交给 node.js 一层,改善 SEO 问题和速度体验。
websocket
websocket 长连接方式不受跨域影响,建立连接之后客户端和服务端都可以主动发送数据,进行交互,在一些业务上还是有需要用到的。
由于 API 不友好,一般使用Socket.io
来进行开发。
总结
- CORS 算是最正统的做法,也是根本解决方案,生产环境最常用
- JSONP 兼容性比较好,但是现在可能比较难用得上
- websocket 某些业务用得上,开发复杂度提升
前端很多问题都有非常多解决方案,但是我总感觉其他方案太黑魔法了,甚至有点厌恶,比如iframe
都有几种办法,canvas
也有,但是不太常用,最常用还是上面几种。