JS —— 跨域问题全解与jsonp 原理理解

来源:互联网 发布:wifi网络测试 编辑:程序博客网 时间:2024/06/09 17:08

前端时间一直在做跨域请求相关的工作,被一些问题困住了,最后不得不用jsonp 方式请求数据,直到今天网上看了下jsonp 的实现原理,才意识到之前的跨域问题实质上还是没有解决。所幸的是,看了几篇不错的资源,算是把跨域相关的问题弄清楚了。


下面将先简单介绍jsonp 原理,之后再整理若不用jsonp ,跨域时可能遇到的问题,及解决方法、相关的知识点。


一、jsonp 原理

背景:

为安全考虑,浏览器会对脚本中的请求进行同源策略检查,跨域XHR 请求时,被请求服务的请求头必须设置Access-Control-Allow-Origin 头等。但对于css、script 文件的引用等,是允许跨域的,也正如此,平常的开发中可在html 头部通过<link>或<script>标签直接引用某些框架站点的js 或css 文件。

jsonp 原理:

jsonp 技术就是利用了<script> 可以任意跨域的特点实现的。即dataType 为jsonp 的请求,实际是创建一个script 标签,通过该标签来实现访问,所以无论设置怎样的请求类型,从浏览器调试窗口可以看到,实际还是为GET 类型的。

假设jsonp 类型请求如下:

$.ajax({url:"url",type:"PUT",//此处设置无效dataType:"jsonp",params:{param1:"paramValue"},success:function myCallBack(data){...}});

隐藏的实际操作为:

1、拼接一个script 标签,<script src="url?param1=paramValue&callbackFunctionName=myCallback"></script>,从而触发对指定地址的GET 请求(所以上面设置的type为PUT 等无效);

2、服务器端对该GET 请求进行处理,并返回字符串"myCallBack(data)";

3、前端加载完该script 资源后,执行myCallBack(data);

4、完成了请求。


二、不用jsonp 的跨域请求

jsonp 是通过script 标签方式实现的,相当于钻了个空子。不用jsonp 又该如何实现跨域访问呢?

首先了解下请求的方式:

1、简单请求

请求类型为HEAD、GET、POST,并满足一定的条件的请求。

2、非简单请求

当然就是除简单请求之外的请求咯。

请求方式详细介绍:阮一峰 网络博客 2种请求方式


两种请求方式的区别:

简单请求直接发送,而非简单请求在实际请求之前,会先发送一个“预检请求”(preflight request),预检请求通过访问控制检查后才会发送需要的请求。


预检请求不能通过检查情况:

预检请求不通过,可能是由于Origin 为null 造成的,此时会提示如下错误:

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.


紧接着问题,什么情况会导致Origin 为null ?

当一个跨域请求从domin A 发送到domin B 时(前提是domin B允许A 跨域请求),可能会被302 重定向到domin C (如需要在domin C验证时),这种情况下,从domin B 到domin C 的跳转请求的请求会导致Origin 为null ,然后提示上面的Origin null 错误,这是个棘手的问题。


关键点,如何解决Origin 为null 问题?

保证两点:

1、domin A 能访问domin B ,domin B 等访问domin C (最基础的一点,即首先两个请求直接发送是都是可以访问的,通过设置Access-Control-Allow-Original 等实现);

2、设置XHR 的withCredentials 为true (必须)。

说明:

第1点是服务器端的设置,第2点是客户端的设置。

服务器端需设置的几个响应头:

Access-Control-Allow-Origin :允许的源,以逗号分隔各源的字符串,如:"www.a.com,www.b.com";

Access-Control-Allow-Methods:允许的请求方式,同上,如:"GET,PUT,POST";

Access-Control-Allow-Credentials:true|false,是否允许传送cookie,需设置为true;

Access-Control-Expose-Headers:允许的请求头。XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。如要想从响应头中获取orgin、accept 头,对应值为:"orgin,accept"。

客户端(即js 代码中)需设置withCredentials 为true:

原始XHR 对象的设置:

var xhr = new XMLHttpRequest();xhr.withCredentials = true;
$.ajax 等方法的设置:

$.ajax({url:"url",type:"POST",dataType:"json",xhrFields:{withCredentials:true},//关键params:{param1:"paramValue"},success:function myCallBack(data){...}});


参考:

1、https://bugs.chromium.org/p/chromium/issues/detail?id=154967

里面部分内容:

In the above reproduction steps, the first request's URL is cross-origin, and its origin is the page URL. The response is a 302 redirect with the appropriate access-control-allow-origin="test1.example.org" header so the redirect is allowed to proceed. As part of the redirect steps, the new request origin is set to "null" and it's URL is set to the value of the Location header.The second request generates a response. The only way this second response can pass the resource sharing check is if it contains this header:access-control-allow-origin="*"Furthermore, the original XHR must not have "allow-credentials" set to true.If no header is present, or if specific origins are specified, the response is rejected. The code seems consistent with the standard here.


2、withCredentials 属性作用:https://developer.mozilla.org/zh-CN/search?q=withCredentials

里面部分内容:

he default is false. XMLHttpRequest from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request.
翻译:

XMLHttpRequest.withCredentials 默认为false,来自不同域的请求不能为当前域设置cookie,除非该请求的withCredentials 属性在最开始就被设置为true 。

CORS 访问默认是不携带cookie 的,所以当需要传认证等信息时,需要将该属性设置为true。

典型的场景:

单点登录。假设dominC 是用于认证的站点,dominA 与dominB 分别为两个系统,且未登录。当访问dominA 时,可能需要调用dominB 的服务,大致过程为:dominA 中AJAX 发送指向dominB 的请求——>判断到dominB 未登录,遂转到dominC ,生成dominB 的令牌——>从dominC 转回dominB ,并设置相关认证信息(需使用cookie)——>调用原始请求的服务,返回最初的请求者dominA。

上面的过程,请求发送到dominB 时,会302重定向到dominC,所以Origin 会变为null,从而让dominC 不知道最初的请求来源于dominA。但跳转的请求回到dominB 时,需要设置cookie,所以必须在请求的最初发起者(dominA)中设置请求的withCredentials 为true 。

若未设置xhrFields:{withCredentials:true},则可能发生预飞请求错误:

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.


3、跨域资源共享标准文档






原创粉丝点击