一次跨域问题引起的思考

问题

今天在联调接口的时候,发现了一个奇怪的错误Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response

刚开始以为只是一次简单的跨域访问,后来在网上查阅资料,发现这是“预检请求”
这里发送的是一次 OPTIONS请求,获得浏览器支持的请求类型,那么何时会进行预检请求呢?

只有在浏览器发送非简单请求的情况下,才会发送预检请求

那么什么样叫做非简单请求,什么又是简单请求呢?

只要同时满足以下两大条件,就属于简单请求。

  1. 请求方法是以下三种方法之一:
    • HEAD
    • GET
    • POST
  2. HTTP的头信息不超出以下几种字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求。

这是该问题所引出的相关名词,本着好奇的原则,继续往下走...

跨域

跨域是针对浏览器端而产生的,服务器端的通信并不会产生这样的问题,这就引出了第一个解决跨域的思路----代理服务器(浏览器请求本地起的服务器—同源服务器,再由后者请求外部服务)并且跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。

代理服务器

我们利用node可以实现代理服务器,笔者是利用了http-proxy-middleware

var proxy = require('http-proxy-middleware');

var apiProxy = proxy('/api', {target: 'http://www.example.org', changeOrigin: true});
复制代码

在这里changeOrigin: true引起了我的兴趣,思考为什么会存在该项配置?这里提到了虚拟主机与独立主机的概念

在我们ping baidu.com的时候会得到IP地址,浏览器通过这个IP地址是可以访问网站的(独立主机),在我们ping apple.com的时候得到的IP地址是不能通过浏览器的方式访问的(共享主机或者虚拟主机) 虚拟主机是一种在单一主机或主机群上,实现多网域服务的方法,可以运行多个网站或服务的技术,因此它可以共享服务器的内存、CPU等资源

虚拟主机主要有以下几种类型

  1. 网址名称对应(Name-based)
  2. IP地址对应(IP-based)
  3. Port端口号对应

http-proxy-middleware中使用的changeOrigin配置项正式由于使用了网址名称对应的虚拟主机。

跨域资源共享(CORS)

在本篇博客中,该种方式会进行详细介绍,利用AJAX进行跨域资源共享的时候,我们就会引出文章刚开始所遇到的问题,简单请求和非简单请求。

  • 简单请求: 在这里为了模仿跨域的请求利用到express框架,先建立两个文件,一个后端接口文件backend.js。

'use strict';
var express = require('express'),
	app = express();

app.get('/auth/:id/', function (req, res) { 
	res.send({ id:req.params.id }); 
});

app.listen(3000);
复制代码

然后利用backend.js指向一个html文件,在html里面发送ajax请求,模仿跨域(端口号不同)

'use strict';

var express = require('express'),
	app = express();

app.use(express.static("./"))
app.get('/', function (req, res) {
	res.render(__dirname + '/index.html');
});

app.listen(4000);
复制代码

最后是html文件,引入了axios

<!DOCTYPE html>
<html>
<head>
	<title></title>
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>

</body>
<script type="text/javascript">
	axios.get('http://localhost:3000/auth/1')
	  .then(function (response) {
	    console.log(response.data);
	  })
	  .catch(function (error) {
	    console.log(error);
	  });
</script>
</html>
复制代码

当发起请求时,我们发现

即使在返回结果是200的情况下,仍然时跨域提醒,这就与前面的观点是一致的 跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。 服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误。 那么解决方法也很简单,在后端加上允许跨域请求。

app.all('*', function(req, res, next) { 
	res.header("Access-Control-Allow-Origin", "*");
	res.header("Content-Type", "application/json;charset=utf-8");
	next(); 
});
复制代码

这样就可以正常访问

  • 非简单请求 依旧利用上面的代码,在发送请求POST时,当头部Content-Typeapplication/json时,代码如下:
<!DOCTYPE html>
<html>
<head>
	<title></title>
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>

</body>
<script type="text/javascript">
	axios.post('http://localhost:3000/auth', {
		name: 'gao'
	}, {
		headers: {
			'Content-Type': 'application/json'
		}
	})
	  .then(function (response) {
	    console.log(response.data);
	  })
	  .catch(function (error) {
	    console.log(error);
	  });
</script>
</html>
复制代码

会发送如图所示的预检请求:

当头部 Content-Typeapplication/x-www-form-urlencoded时,代码如下:

<!DOCTYPE html>
<html>
<head>
	<title></title>
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>

</body>
<script type="text/javascript">
	axios.post('http://localhost:3000/auth', {
		name: 'gao'
	}, {
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded'
		}
	})
	  .then(function (response) {
	    console.log(response.data);
	  })
	  .catch(function (error) {
	    console.log(error);
	  });
</script>
</html>
复制代码

会发现,浏览器不会发送预检请求并且返回成功

那么如何保证在发送预检请求时,浏览器不会报错呢?

后端代码如下:

'use strict';
var express = require('express'),
	app = express();

app.all('*', function(req, res, next) { 
	res.header("Access-Control-Allow-Origin", "*");
	res.header("Access-Control-Allow-Headers", "content-type");
	res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); 
	res.header("Content-Type", "application/json;charset=utf-8");
	next(); 
});

app.get('/auth/:id/', function (req, res) { 
	res.send({ id:req.params.id }); 
});

app.post('/auth', function (req, res) { 
	res.send({ id:req.params.id }); 
});


app.listen(3000);
复制代码

会发现加入了这两行代码即可。

res.header("Access-Control-Allow-Headers", "content-type"); // 这里的content-type是默认的,根据客户端请求来设定
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); 
复制代码

此时POST会发送两次请求:

当预检请求通过后,再次发送 POST请求。

其他

像其他的技术,包括JSONP、LocalStorage、cookie、postMessage在此不多做介绍,可以参考阮大的博客进行参考。

奇淫技巧

在有些时候,后端的接口由于各种不能满足我们所需要的跨域支持,那么就需要前端自己下功夫,我们思考一下,既然浏览器由于安全策略阻止了返回的结果,那么就可以在浏览器的安全策略上进行修改。

设置Chrome浏览器的 disable-web-security, 实现跨域访问后端的接口。

windows

"C:\Users\UserName\AppData\Local\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir

mac

open -a "Google Chrome" --args --disable-web-security --user-data-dir

linux

chromium-browser --disable-web-security

参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值