前端跨域问题相信很多朋友都遇到过,很多时候我们都是直接交给后端来解决。那么为什么会出现跨域问题呢?后端是如何解决跨域问题?
什么是跨域?
广义的跨域包括:
- 资源跳转:超链接跳转、重定向、表单提交
- 资源嵌入:link、ifram、script、img,以及css样式中的background:url()、@font-face()等外链接
- 脚本请求:js的ajax请求、js或DOM 中的跨域操作
狭义的跨域:指浏览器同源策略限制的请求。我们通常所说的也指的是这种。
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。简单来说就是指"协议+域名+端口"三者都相同,如果有一个不同都会导致跨域,即使两个域名指向同一个ip也不会例外。以 http://store.company.com/goods/list
为例
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/goods/detail | 同源 | 只有路径不同 |
http://store.company.com/my/info | 同源 | 只有路径不同 |
https://store.company.com/goods/list | 失败 | 协议不同 |
http://store.company.com:81/goods/list | 失败 | 端口不同 ( http:// 默认端口是80) |
http://news.company.com/goods/list | 失败 | 主机不同 |
跨域的方法
跨域的方法很多,网上案例很多 常见的大概有9种,分别是
- jsonp跨域
- document.domain + iframe
- window.name + iframe
- location.hash + ifram
- 跨域资源共享(CORS)
- WebSocket协议跨域
- HTML5的postMessage跨域
- nginx代理跨域
- nodejs中间件代理跨域
当然,在日常工作中,我们使用最多的还是jsonp和CORS两种,本文也只介绍着两种方式。
1.jsonp跨域
jsonp跨域的原理很简单,就是利用script
标签不受同源策略限制这一特点来实现的。这也是jsonp的为什么只支持get请求,而不支持post请求的原因。通过动态创建script标签连接服务端,服务端通过函数执行的方式将data传递给前端。
代码以原生js为例:
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>
后端node代码实现:
router.get('/article-list', (req, res) => {
let data = {
message: 'success!',
name: req.query.name,
age: req.query.age
}
const fn = req.query.callback
data = JSON.stringify(data)
res.end(fn + '(' + data + ')');
}
2. CORS
上面jsonp的方式已经暴露了一个巨大的缺点,那就是无法解决post请求的跨域。因此,我们更多的是使用cors来解决跨域问题。
跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。
以fetch请求为例, fetch('http://localhost:10086/cors/test1')
scene 1
当我们什么都不做时,此时我们发现,network弹出如下错误:
根据报错提示设置mode,于是我们设置如下fetch('http://baidu.com/x',{mode:'no-cors'})
,此时结果如下
network不再报错了,然而没有返回值,status居然是0。
原来 no-cors
并不是绕过跨域的意思,这也是新手常犯的一个错误
no-cors 保证请求对应的 method 只有 HEAD,GET 或 POST 方法,并且请求的 headers 只能有简单请求头 (simple headers)。如果 ServiceWorker 劫持了此类请求,除了 simple header 之外,不能添加或修改其他 header。另外 JavaScript 不会读取 Response 的任何属性。这样将会确保 ServiceWorker 不会影响 Web 语义(semantics of the Web),同时保证了在跨域时不会发生安全和隐私泄露的问题。
原来,我们需要在服务端设置头信息Access-Control-Allow-Origin
,可以带*,代表wildcard,任何origin都合法,但是有一定的安全风险, 也可以设置具体的origin,本例子则设置为http://localhost:4000
。如果想带多个的话呢?抱歉,没有办法,只能设置单独一个,或者全部都成功。<