JSONP到底是个啥

jsonp跨域原理解析

Posted by 就是我啦 on May 8, 2022

JSONP到底是个啥

原理

优缺点

# 这个东西用的少,原来的理解有欠缺,这次专门整理一下

背景

由于浏览器同源策略的限制,非同源下的请求,都会产生跨域问题,jsonp即是为了解决这个问题出现的一种简便解决方案。

同源策略即:同一协议,同一域名,同一端口号。当其中一个不满足时,我们的请求即会发生跨域问题。

举个简单的例子:

http://www.abc.com:3000到https://www.abc.com:3000的请求会出现跨域(域名、端口相同但协议不同) http://www.abc.com:3000到http://www.abc.com:3001的请求会出现跨域(域名、协议相同但端口不同) http://www.abc.com:3000到http://www.def.com:3000的请求会出现跨域(域名不同)

简单来讲,跨域是指从一个域名的网页去请求另一个域名的资源。比如从百度(http://baidu.com)页面去请求我的博客(blog.pangao.vip)的资源。但由于有同源策略的关系,一般是不允许这么直接访问的。

为什么要有同源策略

试想,如果你刚刚在网银输入账号密码,查看了自己还有1万块钱,紧接着访问一些不规矩的网站,这个网站可以访问刚刚的网银站点,并且获取账号密码,那后果可想而知。所以,从安全的角度来讲,同源策略是有利于保护网站信息的。

为什么要跨域

然而,有一些情况下,我们是需要跨域访问的。比如,程序员开发网站,就有可能需要在本地访问服务器的数据。再比如某公司的A页面(a.pangao.vip)有可能需要获取B页面(b.pangao.vip)。

出于浏览器的同源策略限制。同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。即便两个不同的域名指向同一个ip地址,也非同源。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了

有三个标签是允许跨域加载资源:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

解决方案

1.CORS

CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。CORS 需要浏览器和后端同时支持

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符(*)则表示所有网站都可以访问资源,这时的客户端不需要任何配置即可进行跨域访问。

2.JSONP

细节后面看

3.Nginx代理

JSONP突破同源策略限制:

现在知道了同源策略,那我们就来看下jsonp是如何突破同源策略的限制实现跨域的

首先,不知道大家有没有注意,不管是我们的script标签的src还是img标签的src,或者说link标签的href他们没有被同源策略所限制,比如我们有可能使用一个网络上的图片,就可以请求得到

<img src="https://ss3.baidu.com/9fo3dSag_xI4khGko9WTAnF6hhy/image/h%3D300/sign=6d0bf83bda00baa1a52c41bb7711b9b1/0b55b319ebc4b745b19f82c1c4fc1e178b8215d9.jpg">

src或href链接的静态资源,本质上来说也是一个get请求,拿csdn上的静态资源举例:

img

可以看到,确实是个get请求无疑。同理img标签的src和link标签的href也会发送一个get请求去请求静态资源。那么我们通过这点,是不是发现了点什么,这些标签的src和link属性,并没有受同源策略的限制。说到这里jsonp的实现原理就浮出水面了。

jsonp就是使用同源策略这一“漏洞”,实现的跨域请求(这也是jsonp跨域只能用get请求的原因所在)。想象一下,既然是个get请求,那么服务端一定可以接收到,并做出反馈。ok,知道这两点之后,我们开始具体使用jsonp进行跨域请求。

JSONP跨域实现

根据上边所说的,我们要用过利用srcipt标签的src属性来实现,那么我们如何做呢,我们来看一段简单的代码,为了方便,我这里使用jQuery:

$('#btn').click(function(){
			var frame = document.createElement('script');
			frame.src = 'http://localhost:3000/article-list?name=leo&age=30&callback=func';
			$('body').append(frame);
		});

可以看到,让我们点击按钮的时候,创建了一个script标签(即会发送一个get请求到src指向的地址,注意:这里必须使用scipt标签,否则返回的数据不会被当作js执行),src地址是”localhost:3000/article-list”,这个src地址,就是我们请求的服务端接口。注意,这里我们有三个参数,name,age和callback,name和age不说了,这跟我们平时普通的get请求参数无异。主要说下callback这个参数,callback参数就是核心所在。为什么要定义callback呢?首先我们知道,这个get请求已经被发出去了,那么我们如何接口请求回来的数据呢,callback=func则可以帮我们做这件事。我们继续看下边的代码

<button id="btn">点击</button>
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
  <script>
    $('#btn').click(function(){
			var frame = document.createElement('script');
			frame.src = 'http://localhost:3000/article-list?name=leo&age=30&callback=func';
			$('body').append(frame);
		});
		
		function func(res){
			alert(res.message+res.name+'你已经'+res.age+'岁了');
		}
  </script>

这里可以看到,我们声明了一个func函数,但没有执行,你可以想一下,如果服务端接口到get请求,返回的是func({message:’hello’}),这样的话在服务端不就可以把数据通过函数执行传参的方式实现数据传递了吗。

服务端代码实现:

接下来,我们看服务端应该如何实现:

router.get('/article-list', (req, res) => {
  console.log(req.query, '123');
  let data = {
    message: 'success!',
    name: req.query.name,
    age: req.query.age
  }
  data = JSON.stringify(data)
  res.end('func(' + data + ')');
});

ok,接下来当我们点击提交的时候,就获取到了服务端反回的数据。如下:

img

优点

  • 它不像 XMLHttpRequest 对象实现的 Ajax 请求那样受到同源策略的限制,JSONP 可以跨越同源策略

  • 它的兼容性更好,在更加古老的浏览器中都可以运行不需要 XMLHttpRequest 或 ActiveX 的支持

  • 在请求完毕后可以通过调用 callback 的方式回传结果。将回调方法的权限给了调用方。这个就相当于将 controller 层和 view 层终于分 开了。我提供的 jsonp 服务只提供纯服务的数据,至于提供服务以 后的页面渲染和后续 view 操作都由调用者来自己定义就好了。如果有两个页面需要渲染同一份数据,你们只需要有不同的渲染逻辑就可以了,逻辑都可以使用同 一个 jsonp 服务。

缺点

  • 只支持 GET 请求而不支持 POST 等其它类型的 HTTP 请求
  • 它只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面之间如何进行 JavaScript 调用的问题。
  • jsonp 在调用失败的时候不会返回各种 HTTP 状态码
  • 缺点是安全性。万一假如提供 jsonp 的服务存在页面注入漏洞,即它返回的 javascript 的内容被人控制的。那么结果是什么?所有调用这个 jsonp 的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用 jsonp 的时候必须要保证使用的 jsonp 服务必须是安全可信的

总结

  • 需要注意的是,callback参数定义的方法是需要前后端定义好的,具体什么名字,商讨好就可以了。其实jsonp的整个过程就类似于前端声明好一个函数,后端返回执行函数。执行函数参数中携带所需的数据,整个过程实际非常简单易懂