翻译自 文章
相信大家都已经了解什么是跨域请求,知道它们为什么会发生和如何避免它们。有时候,当讨论跨域请求得我时候,人们习惯使用JSONP(JSON with Pdding)作为跨域请求的解决方案,在这篇文章中,我们将会解释一下为什么JSONP不是最好的解决方案和为什么它不应该被使用。
CORS 101 - 一个快速的概括
当从一个网页应用发起请求资源,而这个应用所在域名不属于我们控制之下的,我们就会收到一条提示Failed to load resource: Origin * is not allowed by Access-Control-Allow-Origin.
。这意味着浏览器阻止我们访问给定资源的请求 - 该资源是一个API的端。
一个实现的例子
现在就用一个例子来演示一下什么是跨域,我们有两个Node.js文件,一个是API服务端和一个是HTTP服务端:
1 | // api.js |
1 | // http-server.js |
请注意,HTTP服务端是一个直截了当的实现,它用了
fs.readFileSync
函数,调用的时候会造成堵塞,在实际的生产环境中不应该使用。实际上,我们应该使用npm来管理。
现在我们有两个Node.js程序,一个是API服务端,运行在3000端口杀红,一个是HTTP服务端,运行在8080端口中。由于CORS背后的规则,这l两个服务会被认为是两个独立的实体,因此浏览器将阻止访问资源。
我们将会加入一个简单的HTML文件,使用JQuery制造一个AJAX请求来演示一下:
1 |
|
打开浏览器,并且访问localhost:8080
,就会看到前面所说的CORS提示信息了。而实际上,这个也是我们预期的结果。
JSONP
来看一下在这个情景下JSONP是怎么实现的。首先留意我们的index.html
文件,有一样十分有趣的事情,我们实际上也引用了一个不在同域下的资源 —— JQuery库,而它是存在于一个CDN上的。
这就引出了一个很有趣的观点,就是我们可以通过<script></script>
标签来访问资源。
我们需要对API服务返回给我们的数据作同样的修改。这就是Padding
在JSONP中的含义。浏览器可以执行到这个函数。
来看看更新后的API服务端代码:1
2
3const apiHandler = (request, response) => {
response.jsonp({ message: 'Hello World!'})
}
同样的,更新index.html
:1
2
3
4$.ajax({
url: 'http://localhost:3000/api/htllo',
dataType: 'jsonp'
}).done(data => console.log(data))
当我们刷新页面,我们可以看到数据有输出。可以进一步把数据输出到页面中:1
2
3
4$.ajax({
url: 'http://localhost:3000/api/hello',
dataType: 'jsonp'
}).done(data => document.getElementById('message').textContent = data.message)
这个就是一个完整的例子。
但是我们今天要讨论的是为什么JSONP是个坏东西,并且为什么我们不要去用它。
只支持HTTP GET
JSONP只支持HTTP GET
请求,其它的请求一概不支持。这是由于<script></script>
标签只能发起HTTP GET
请求。
没有错误处理
当调用AJAX请求的时候,我们可以获取到由服务端返回的错误body,然而,使用JSONP的话,我们要么得到的是CORS错误,要么得到的是404错误,不管是哪种,对于我们来说调试都是很困难的。
脆弱性
JSONP暴露了很多漏洞,它假定了传送回来的代码是可信的,进一步暴露了CSRF(跨站点请求伪造)漏洞。
综上所述,使用JSONP并不是一个很好的解决方案。
更多的选择
那么,如果不适用JSONP的话,我们还能用什么呢?请再看一遍“什么是CORS?”,里面提出了一系列的解决方案,包括使用代理和CORS包。
对于我们的例子来说,我们有两个选择去支持CORS:1
2
3
4
5
6
7
8//api.js
const apiHandler = (req, res) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Request-Method', '*')
res.header('Access-Control-Allow-Methods', 'OPTIONS, GET')
res.header('Access-Control-llow-Headers', '*')
res.json({ message: 'Hello World!'})
}
调用Ajax的时候不要忘记了移除
dataType: 'jsonp'
请注意直接使用(*)
并不是一个好的方案,指定允许域名更加妥当。
另外的解决方案就是使用cors
包,安装了之后,可以把它作为一个中间件引入:1
2
3
4
5// api.js
const cors = require('cors')
app.use(cors())
// or
app.get('/api/hello', cors(), apiHandler)
总结
毫无疑问,JSONP可以被视为克服导致CORS错误的某些情况的一种有用方法,但是它有更多的负面副作用,以及安全漏洞,使用代理或相关的CORS包更好的应对跨域问题。