前端跨域问题我想很多同学遇到过,或者是刚刚请求数据成功, 然而转眼之后就会报错
XMLHttpRequest cannot load http://www.server.com/server…. No ‘Access-Control-Allow-Origin’ header is present on the requested resource.Origin ‘http://www.client.com’ is therefore not allowed access.
我不知道大家有没有遇到过反正我是遇到过,不管在什么时候你都有可能遇到跨域
跨域是什么?
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
为什么会产生跨域?
产生跨域的主要原因是因为同源策略,什么是同源策略呢?我们来看下面的解释
同源策略 /SOP(Same origin policy)是一种约定,由 Netscape 公司 1995 年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。所谓同源是指 ” 协议 + 域名 + 端口 ” 三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源,看看下面的产生跨域的场景你就会明白同源策略的含义。
那么有的人会问了,同源策略会产生跨域为什么还要存在呢?平常我们访问网站的时候都是一个地址对应相同的内容,如果同源策略不存在网站的 dom 很有可能被钓鱼网站复制,那样你就会上当。
那有的人又问了有了同源策略就安全吗?不是有了它就安全是因为同源策略是基础的安全机制,面对强大的攻击还是需要强大的攻防的
跨域场景
url | 说明 | 是否可以通信 |
---|---|---|
http://www.kuayu.com/img.jpg | 同一域名,不同文件,不同路径 | 可以 |
http://www.kuayu.com/img2.jpg | 同一域名,不同文件,不同路径 | 可以 |
url | 说明 | 是否可以通信 |
http://www.kuayu.com:6666/img.jpg | 同一域名,不同端口 | 不可以 |
http://www.kuayu.com/img2.jpg | 同一域名,不同端口 | 不可以 |
url | 说明 | 是否可以通信 |
https://www.kuayu.com/img.jpg | 同一域名,不同协议 | 不可以 |
url | 说明 | 是否可以通信 |
http://www.kuayu.com/img.jpg | 主域相同,子域不同 | 不可以 |
http://kuayu11.com/img2.jpg | 主域相同,子域不同 | 不可以 |
跨域的解决办法
-
jsonp
Jsonp(JSON with Padding) 是 json 的一种 ” 使用模式 ”,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
为什么我们从不同的域(网站)访问数据需要一个特殊的技术 (JSONP) 呢?这是因为同源策略。
同源策略,它是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript
的浏览器都会使用这个策略。同源策略上面咱们已经简单的价绍过了,详细请看上面首先我们先设置 script 标签,一个简单的 jsonp 实现,其实就是拼接 url,然后将动态添加一个 script 元素到头部,我来看下面菜鸟教程给与的客户端例子
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JSONP 实例 </title> </head> <body> <div id="divCustomers"></div> <script type="text/javascript"> // 创建函数 function callbackFunction(result, methodName) { // 声明 ul 标签 var html = '<ul>'; // 循环创建 li 插入 ul for(var i = 0; i < result.length; i++) {html += '<li>' + result[i] + '</li>'; } html += '</ul>'; // 写入 html document.getElementById('divCustomers').innerHTML = html; } </script> // 使用函数 <script type="text/javascript" src="http://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction"></script> </body> </html>
我们再来封装一下简单的 jsonp 函数完整的代码如下面
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JSONP 实例 </title> </head> <body> <div id="divCustomers"></div> <script type="text/javascript"> function jsonp(req){ // 创建 script 的标签 var script = document.createElement('script'); // 拼接 url var url = req.url + '?callback=' + req.callback.name; // 赋值 url script.src = url; // 放入头部 document.getElementsByTagName('head')[0].appendChild(script); } </script> </body> </html> 上面的代码使用方式和咱们菜鸟教程生成的方式是一样的
后台解决跨域
一般有两种解决方法 1. 后台返回的数据格式改成 jsonp 的形式 2. 后台 response 添加 header,response.setHeader("Access-Control-Allow-Origin", "*"); "Access-Control-Allow-Origin", "*" 表示所有网站都可以访问 或者可以指定某个具体域名访问 response.setHeader("Access-Control-Allow-Origin", "url")
我们来看看后台代码 node + koa 的实现,我们可以通过设置 header 的 Access-Control-Allow-Origin
就像是我们设置的 token,设置的 json 格式或是其它格式啊,当然下面的代码是通过使用 koa-cors 插件进行的解决跨域问题。
var koa = require('koa');
var route = require('koa-route');
var cors = require('koa-cors');
var app = koa();
app.use(cors());
app.use(route.get('/', function() {this.body = { msg: 'Hello World!'};
}));
app.listen(3000)
其实这一切都归根揭底为 cors,那么它是什么呢? 它为什么可以解决跨域的问题呢?那我们具体来看看它到底是什么
cros:跨域资源共享 浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple
request),咱们在这里只说一下简单的请求方式:
请求方法是以下三种方法之一:
HEAD
GET
POST
HTTP 的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时 Origin 字段的值,要么是一个 *,表示接受任意域名的请求。
Access-Control-Allow-Credentials
值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中。设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发给服务器。这个值也只能设为 true,如果服务器不要浏览器发送 Cookie,删除该字段即可
vue 解决跨域方案
我们可以在 vue-cli 配置文件里面设置一个代理,跨域的方法有很多,通常需要后台来进行配置。
我们可以直接通过 node.js 代理服务器来实现跨域请求。
接下来我们可以通过 vue 项目中 config 文件夹下的 index.js 配置文件
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// Various Dev Server settings
host: '0.0.0.0', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
.......
...
}
上面是我从 vue 项目中拿出来的代码供大家便于寻找, 接下来我们需要新建一个 js 文件,名为 proxy.js 配置代码如下:
module.exports = {
proxy: {
'/api': { // 将 www.qj.com 映射 /apiUrl
target: 'https://www.qj.com', // 接口域名
secure: false, // 如果是 https 接口,需要配置这个参数
changeOrigin: true, // 是否跨域
pathRewrite: {'^/api': '' // 路径重写}
}
}
}
这把我们需要把 proxy.js 引入到 config 文件夹下的 index.js 中,var proxy = require('./proxy.js')
, 然后将 proxyTable: proxy.proxy
插入到我们的 dev 对象中进行跨域的使用,我们这时候如果设置完了,跨域还是没有解决怎么办,有时候还得需要更改咱们本地的 hosts 文件以达到我们解决跨域的目的
总结
其实在我开发的经历中遇到跨域的事情还是较少的,一般遇到跨域的时候,基本上都是又后台来处理,以前也会遇到但是使用 jsonp 的时候还是比较少的,以及上面我们提到的 vue-cli 的设置解决跨域的问题,我们看到是直接设置 webpack 的方式来解决,由于 vue 开发比较多,所以还是比较关注 vue 的解决方法,有的时候还碰到过比如我们开发 web 页面和小程序,在小程序请求都没有 went,但是到 web 也会造成跨域问题,所以我们只需设置前台解决跨域问题就行,或者更安全的方式直接让后台一次性解决也免得不小心手机端也跨域了呢
参考资料
正确面对跨域,别慌
菜鸟教程
jsonp 跨域请求详解——从繁至简
阮一峰 cros