跨域

来源:互联网 发布:java中compareto 编辑:程序博客网 时间:2024/06/05 23:43

基础概念

同源策略和跨域概念

同源策略(Same-orgin policy)限制了一个源(orgin)中加载脚本或脚本与来自其他源(orgin)中资源的交互方式。
如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(orgin)。
同源之外的请求都可以称之为跨域请求。

为什么浏览器要限制跨域访问呢?

原因就是安全问题:如果一个网页可以随意地访问另外一个网站的资源,那么就有可能在客户完全不知情的情况下出现安全问题。比如下面的操作就有安全问题:
用户访问www.mybank.com ,登陆并进行网银操作,这时cookie啥的都生成并存放在浏览器
用户突然想起件事,并迷迷糊糊地访问了一个邪恶的网站 www.xiee.com
这时该网站就可以在它的页面中,拿到银行的cookie,比如用户名,登陆token等,然后发起对www.mybank.com 的操作。
如果这时浏览器不予限制,并且银行也没有做响应的安全处理的话,那么用户的信息有可能就这么泄露了。

跨域资源共享CORS

跨域资源共享(CORS)是一份浏览器技术的规范,提供了Web服务器从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是JSONP模式的现代版。与JSONP不同,CORS除了支持GET方法以外,还支持其他HTTP方法。用CORS可以让网页设计师用一般的XMLHTTPRequest,这种方式的错误处理比JSONP要来的好。另一方面,JSONP可以在不支持CORS的老旧浏览器上运作,现代的浏览器都支持CORS。

浏览器发现某次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。Origin: http://api.bob.com ,服务器根据这个值,决定是否同意这次请求。如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest.onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com //必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。Access-Control-Allow-Credentials: true //可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。Access-Control-Expose-Headers: FooBarContent-Type: text/html; charset=utf-8

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

Access-Control-Allow-Credentials: true

另一方面,开发者必须在AJAX请求中打开withCredentials属性。

var xhr = new XMLHttpRequest();xhr.withCredentials = true;

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。

xhr.withCredentials = false;

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

跨浏览器的CORS

function createCORSRequest (method, url) {    var xhr = new XMLHttpRequest();    if ('withCredentials' in xhr) {        xhr.open(method, url, true);    } else if (typeof XDomainRequest != 'undefined') { //IE8+        xhr = new XDomainRequest();        xhr.open(method, url);    } else {        xhr = null;    }    return xhr;}var request = createCORSRequest('get', 'http://www.xxxxxxx.xxx'); if (request) {    request.onload = function() {        //对 request.responseText进行处理    };    request.send() }

如果发送的非简单请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

“预检”请求的HTTP头信息。

OPTIONS /cors HTTP/1.1Origin: http://api.bob.comAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...

“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

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

HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderContent-Type: text/html; charset=utf-8Content-Encoding: gzipContent-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plain

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

XMLHttpRequest cannot load http://api.alice.com.Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

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

CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

JSONP概念

由于同源策略,一般来说不允许JavaScript跨域访问其他服务器的页面对象,但是HTML的<script>元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

JSONP全称为:JSON with Padding,可用于解决主流浏览器的跨域数据访问的问题。

Web 页面上调用 js 文件不受浏览器同源策略的影响,所以通过 Script 便签可以进行跨域的请求:

  • 首先前端先设置好回调函数,并将其作为 url 的参数。
  • 服务端接收到请求后,返回数据时会将这个callback参数作为函数名来包裹住JSON数据,作为动态js文件返给客户端,这样客户端就可以随意定制自己的函数来自动处理返回数据了
  • 收到结果后因为是 script 标签,所以浏览器会当做是脚本进行运行,从而达到跨域获取数据的目的。
<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>index.html</title></head><body>    <script>    function jsonp(req){        var script = document.createElement('script');        var url = req.url + '?callback=' + req.callback.name;        script.src = url;        document.getElementsByTagName('head')[0].appendChild(script);     }    function jsonpCallback(data) {        alert('获得 X 数据:' + data.x);    }    jsonp({        url : '',        callback : jsonpCallback    });    </script></body></html>//server.jsconst url = require('url');require('http').createServer((req, res) => {    const data = {    x: 10    };    const callback = url.parse(req.url, true).query.callback;    res.writeHead(200);    res.end(`${callback}(${JSON.stringify(data)})`);}).listen(3000, '127.0.0.1');console.log('启动服务,监听 127.0.0.1:3000');

缺点:

  • 它支持 GET 请求而不支持 POST 等其它类行的 HTTP 请求。
  • 它只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面或 iframe 之间进行数据通信的问题
  • 从其它域加载代码执行,可能不安全
  • 不容易确定jsonp请求是否失败

ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本。

jsonp 动态js文件
正常的引入js文件是如何引入的?

demo.html

<script src="script.js"></script>

如果script.js内容如下会发生什么?

alert(1);

显然,demo.html中会弹出1

第一次改造:

如果demo.html改成

<script>    function show(data){        alert(data);    }</script><script src="script.js"></script>

script.js

show(1);

同样会弹出1

再变形
demo.html

<script>    function show(data){        console.log(data);    }</script><script src="script.js"></script>

script.js

show({    a:1})

控制台就会输出{a:1}这个对象

恩,看到这里还不明白jsonp的原理?

继续:
demo.html

<script>    function show(data){        console.log(data);    }</script><script src="script.php?backname=show"></script>

这里的script.js不再是静态js,而是一个服务器动态输出的js文件,并且带了一个参数,这个参数给后台,告诉后台,以什么名字返回数据

script.php

$back=$_GET['backname'];echo $back."({'a':1})";
0 0