跨域之 JSONP 与 CORS

前言

跨域问题是前端经常遇到的问题,并且也是面试的时候面试官会经常问到的问题,那么今天就来总结一下跨域及跨域的几种方式。

什么是跨域

跨域问题的出现主要是因为浏览器的同源策略,即浏览器只能访问与包含它的页面处于同一个域中的资源。什么是同一个域呢
http://www.mywebsit.com:8080
其中的 http 是协议,同样的还有 httpsftp 等,www.mywebsite.com 是域名,8080 是端口号,只有同一个协议,域名,端口下的资源才可以相互访问,如
源地址为 http://www.mywebsit.com:8080

请求地址 方式 结果
http://www.mywebsit.com:8080/test/test.html 同一域名,不同文件夹 成功
https://www.mywebsit.com:8080/ 协议不同 失败
http://www.mywebsit.com:8081/ 端口号不同 失败
http://www.MyWebsit.com:8080/ 域名不同 失败

如果没有浏览器的同源策略,那么就会带来许多问题,比如 CSRF 攻击等,可以参考 浅析 web 安全之 XSS 和 CSRF
但是,合理的跨域请求对于开发中是至关重要的,接下来就讲讲常见的两种处理跨域的方式

JSONP

JSONP 是 JSON with padding 的简写,即填充式或参数式 JSON,是应用 JSON 的一种方式,JSONP 与 JSON 差不多,只不过是被包含在函数调用中的 JSON,就像下面这样

callback({"name": "Nicholas"})

JSONP 由两部分组成,回调函数和数据,回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。下面是一个典型的 JSONP 请求。

http://www.mywebsit.com/json/?callback=handleResponse

这里指定的回调函数的名字叫handleResponse()
JSONP 是通过动态 script 元素来使用的,使用时可以为 src 属性指定一个跨域 URL。这里的 script 元素与 img 元素类似,都有能力不受限制地从其他域加载资源。因为 JSONP 是有效的 javascript 代码,所以在请求完成后,即在 JSONP 响应加载到页面以后,就会立即执行。来看一个例子

前端

function handleResponse(response){
    alert(response.name);
}
var script = document.createElement("script");
script.src = "http://www.mywebsit.com/json/?callback=handleResponse";
document.body.insertBefore(scrpit,document.body.firstChild);

后端

 public void exchangeJson(HttpServletRequest request,HttpServletResponse response) {  
        PrintWriter out = response.getWriter();   
        //客户端请求参数  
        String callback = request.getParameter("callback");
        //返回jsonp格式数据  
        out.println(callback+"("+{"name": "Nicholas"}+")");
        out.flush();  
        out.close();  
    }  

这里演示了最简单的一个例子,后端获取到请求,将 callback 里面的值保存到 callback 这个字符串当中,最后返回数据的时候返回 callback+"("+{"name": "Nicholas"}+")",即 handleResponse({"name": "Nicholas"}),所以前端接收到这段数据就会当成代码执行,显示结果

CORS

CORS ( Cross-Origin Resource Sharing ) 即跨域资源共享是解决跨域问题的一个很好的方案,它只需要浏览器和服务器双方都支持这个功能,而且全程不需要用户参与。对于前端开发者来说也不需要额外操作,可以像发起普通 ajax 请求一样,浏览器发现跨域请求会自动添加一些附加的头信息,有时会多出一次附加的请求。
因此,CORS 的关键是服务器,只要服务器实现了 CORS 的接口,就可以跨源通信。

简单请求与非简单请求

浏览器将 CORS 请求分成两类:简单请求和非简单请求,只要同时满足下面两个条件就属于简单请求
( 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

只要不满足以上两个条件就属于非简单请求,浏览器对于非简单请求会进行一次预检

简单请求

对于简单请求,浏览器直接发出 CORS 请求,具体来说,就是在 CORS 请求头中增加一个 Origin 字段

下面是一个请求的例子

GET /resources/public-data/ HTTP/1.1
Host: bar.other
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1
Origin: http://foo.example

上面的头信息中,Origin 字段用来说明本次请求来自于哪个源(协议 + 域名 + 端口)。服务器根据这个值是否在许可范围内,决定是否同意这次请求

如果 Origin 指定的源不在许可范围内,服务器会返回一个正常的 HTTP 回应,浏览器会发现这个回应的头信息没有包含 Access-Control-Allow-Origin 字段里,就知道出错了,会被 XMLHttpRequest 的 onerror 回调函数捕获。

如果 Origin 指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与 CORS 请求相关的字段,都以 Access-Control- 开头

  • Access-Control-Allow-Origin 该字段是必须的,值是 * 或者请求时的 Origin 字段的值,* 的话代表接收任意域名的请求
  • Access-Control-Allow-Credentials 该字段是可选的,表示是否允许发送 Cookie,默认情况下不包含 Cookie
  • Access-Control-Expose-Headers 该字段也是可选的。CORS 请求时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定。上面的例子指定,getResponseHeader(‘FooBar’) 可以返回 FooBar 字段的值。

非简单请求

预检请求

非简单请求指的是对服务器有特殊要求,比如请求方法为 PUT 或 DELETE,或者 Content-Type 字段的类型是 application/json。

非简单请求的 CORS 请求会在通信之前,增减一次 HTTP 查询的请求,成为 “预检”。

浏览器会先询问服务器,当前网页所在的域名是否在服务器许可的名单之中,以及可以使用哪些 HTTP 请求和头部字段。如果通过服务器的校验,才会发起正式的 XMLHttpRequest 请求,否则就报错

var url = 'http://bar.other/resources/put-here/';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send(null);

上面的代码中,请求的方法是 PUT,并且发送一个自定义请求头信息 X-Custom-Header。

浏览器发现这是一个非简单请求,就会自动发出一个 “预检” 请求,要求服务器确认可以这样请求,下面是这个预检请求的 HTTP 头信息

OPTIONS /resources/public-data/ HTTP/1.1
Origin: http://foo.example
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: bar.other.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

预检请求用的方法是 OPTIONS,表示这个请求是用来询问的。请求头里面的关键字段是 Origin,表示来自哪个源。
除了 Origin 字段,预检请求的头信息包括两个特殊字段
(1)Access-Control-Request-Method 该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法,上例是 PUT。

(2)Access-Control-Request-Headers 该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段,上例是 X-Custom-Header。

预检请求的回应

服务器收到预检请求之后,检查了 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers 字段以后,确认允许跨源请求,就可以做出回应。

响应头如下

HTTP/1.1 200 OK
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
  • Allow-Control-Access-Origin 必需,表示可以请求的源。
  • Access-Control-Allow-Methods 必需,表示支持的所有方法,以逗号分隔
  • Access-Control-Allow-Headers 如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段。
  • Access-Control-Max-Age 可选,单位为秒,即即允许缓存该条回应 1728000 秒,在此期间不用发出另一条预检请求

如果服务器否定了预检请求,会返回一个正常的 HTTP 响应,但是这个响应中没有任何 CORS 相关的头信息字段。这时,浏览器就会认定服务器不同意预检请求,因此触发一个错误,被 XMLHttpRequest 对象的 onerror 回调函数捕获

浏览器的正常请求和回应

一旦浏览器通过了预检请求,则以后每次浏览器正常的 CORS 请求都和简单请求一样,有一个 Origin 的字段,服务器的响应也都会有 Access-Control-Allow-Origin 头信息字段

区别

JSONP 只支持 GET 请求,CORS 支持所有类型的 HTTP 请求

总结及面试

了解跨域吗或者了解 JSONP 或 CORS

跨域是因为浏览器的同源策略导致的不同协议,域名,端口之间的资源无法正常访问的问题。主要解决方法有 JSONP 和 CORS。

JSONP 的话主要是通过动态生成的 script 标签带上回调函数的名字,传给后台,后台将数据包裹在回调函数里面返回前台,然后执行回调函数。

CORS 的话要求浏览器和服务器都支持这个功能,然后浏览器就可以发送简单请求和非简单请求。

对于简单请求来说,浏览器直接发送请求,请求中携带 Origin 字段。如果该字段在服务器允许的列表中,则服务器响应该请求,并在响应头里面带上 Access-Control-Allow-Origin。若不在的话,则会被请求的 onerror 函数捕获。

对于非简单请求,浏览器先发送 option 预检请求并带上请求的方法和参数,如果服务器允许这些方法和参数,会返回对应的请求。如果不允许的话,会发送一个正常的 HTTP 响应,但是这个响应中没有 CORS 相关的信息。然后预检通过的话,浏览器就像发送简单请求一样发送请求。

JSONP 与 CORS 的区别

主要区别就是 JSONP 只能发起 get 请求,而 cors 可以发起任何请求

其他可以跨域的标签

img 和 iframe,所有具有 src 属性的都可以

其他跨域方式

Web Socket,具体的可以搜索相关文章,就不详细讲解了

猜你喜欢

转载自blog.csdn.net/zhang6223284/article/details/81432345