在我们日常的工作开发中,经常遇到跨域问题,特别是前后端分离的开发模式下,跨域问题也是非常常见的.
在JavaScript中,有一个很重要的安全性限制,被称为“Same-Origin Policy”(同源策略)。这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript只能访问与包含它的文档在同一域下的内容。
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。
浏览器的同源策略,是对JavaScript实施的安全限制。
所谓的同源是指,域名、协议、端口均为相同。
当前页面url | 被请求页面url | 是否跨域 | 原因 |
---|---|---|---|
http://www.hello.com/ | http://www.hello.com/a.html | 否 | 同源(协议,域名,端口相同) |
http://www.hello.com/ | https://www.hello.com/a.html | 跨域 | 协议不同(http/https) |
http://www.hello.com/ | http://sz.hello.com/ | 跨域 | 子域名不同(www/sz) |
http://www.hello.com:8080 | http://www.hello.com:7070 | 跨域 | 端口号不同(8080/7070) |
http://localhost:8080 | http://127.0.0.1:8080 | 跨域 | 子域名不同(localhost/127.0.0.1) |
几种主流的跨域解决方案
jsonp是一种跨域通信的手段,它的原理其实很简单:
a:首先是利用script标签的src属性来实现跨域。
b:通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务器端向客 户端通信。
c: 由于使用script标签的src属性,因此只支持get方法
//创建script标签
function createScript(){
//定义一个script标签
let oScript = document.createElement("script");
//script的src属性 直接赋值为需要跨域的接口链接
//并且定义callback参数,后面跟一个回调函数
oScript.src = `https://api.asilu.com/today?callback=getTodays`;
//将生成的script追加到我们的dom中
document.body.appendChild(oScript);
}
//callback对于的函数
function getTodays(data) {
//获取跨域得到的对象数据
let oTodays = data.data;
}
上述就是通过jsonp 实现了一个跨域请求,获取天气的数据.既然叫jsonp,显然目的还是json,而且是跨域获取,利用js创建一个script标签,把json的url赋给script的scr属性,把这个script插入到页面里,让浏览器去跨域获取资源,callback是页面存在的回调方法,参数就是想得到的json,回调方法要遵从服务端的约定一般是用 callback 或者 cb,特别注意的是jsonp只针对get形式的请求
优点:使用简便,没有兼容性问题。
缺点:
只支持 GET 请求。
前后端需要配合。
由于是从其它域中加载代码执行,因此如果其他域不安全,很可能会在响应中夹带一些恶意代码。
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)- 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制- 整个CORS通信过程,都是浏览器自动完成,不需要用户参与- 对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样- 实现CORS通信的关键是服务端,只要服务端实现了CORS接口,就可以跨源通信
实现CORS并不难,只需服务端做一些设置即可:如
// 控制允许的请求来源
header("Access-Control-Allow-Origin:*"); // *表示允许任何来源 基本上设置这个就可以了
//另外,还有一些需要设置put请求 delete请求的,还有自定义请求头,nodejs默认是get,post
// 控制允许的自定义请求头
'Access-Control-Allow-Headers': 'abc,efg',
// 控制允许的请求方式
'Access-Control-Allow-Methods': 'GET,POST,PUT,PATCH,DELETE'
注意:IE10以下不支持CORS
优点:
CORS 通信与同源的 AJAX 通信没有差别,代码完全一样,容易维护。
支持所有类型的 HTTP 请求。
缺点
存在兼容性问题,特别是 IE10 以下的浏览器。
第一次发送非简单请求时会多一次请求。
通过服务端语言代理请求。如PHP,C# 服务端语言是没有跨域限制的。- 让服务器去别的网站获取内容然后返回页面。因为同源策略是针对浏览器的安全性限制.服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。
我们在请求第三方的数据时,通过浏览器的ajax请求肯定是不行的,因为人家也不会给你设置cors,所以我们这边的服务器代理解决跨域的方式就应运而生了.
我这边通过nodejs+express实现服务代理跨域
需要安装中间件 http-proxy-middleware
$ npm install http-proxy-middleware
服务端代码
// 通过代理解决跨域问题
/**
* 使用nodejs启动一个代理服务
* 第三方的 http-proxy-middleware 中间件
*/
//引入express
const express = require('express');
//引入代理中间件
const { createProxyMiddleware } = require('http-proxy-middleware');
//声明express实例
const app = express();
// 注册一个cors的中间件,允许跨域
app.use((req, res, next) => {
res.set({
//允许所有域请求,实际开发中,*需要改成对应的域名
'Access-Control-Allow-Origin': '*',
// 设置允许前端传递的那些自定义请求头
'Access-Control-Allow-Headers': 'token',
// 设置允许的请求方式
'Access-Control-Allow-Methods': 'GET,POST,PUT,PATCH,DELETE'
});
//必须要写,将控制权交给下一个
next();
});
// 注册一个代理中间件
// 前端服务:http://localhost:8080
// 代理服务:http://localhost:3000
// 目标服务:http://m.maoyan.com
app.use('/api', createProxyMiddleware({
//配置选项
// 目标地址:只需要协议和主机
target: "http://m.maoyan.com",
/**
* 路径重写
*
* 当我们通过代理访问目标接口的流程大致如下
* http://localhost:8080 -> 请求 ->
* http://localhost:3000/api/ajax/movieOnInfoList -> 代理到 ->
* http://m.maoyan.com/api/ajax/movieOnInfoList
* 这时真实的目标接口地址是没有 /api 这个前缀的。所以会导致请求失败 404 等问题
*
* 设置如下的路径重写规则之后,流程如下
* http://localhost:8080 -> 请求 ->
* http://localhost:3000/api/ajax/movieOnInfoList -> 代理到 ->
* http://m.maoyan.com/ajax/movieOnInfoList
*/
pathRewrite: {
"^/api": ""
},
// 虚拟托管站点所需, 一般直接设置为true就好
changeOrigin: true
}));
//监听3000端口,启动服务
app.listen(3000, () => {
console.log("代理服务启动成功");
});
前端代码
<script>
$(function () {
$.ajax({
url: 'http://localhost:3000/api/ajax/movieOnInfoList',
type: 'GET',
success: function (res) {
console.log(res)
}
})
})
</script>
上面是3中主流的跨域解决方案,还有其他的解决方式,有兴趣可以大家去研究:JSONP、CORS、服务器代理、document.domain、window.name、location.hash、postMessage ,nginx代理跨域