前端跨域的那些事儿

同源策略:

三同:协议同,域名同,端口号同

下表给出了相对http://a.xyz.com/dir/page.html同源检测的示例:

URL结果原因
http://a.xyz.com/dir2/other.html成功 
http://a.xyz.com/dir/inner/another.html成功 
https://a.xyz.com/secure.html失败不同协议 ( https和http )
http://a.xyz.com:81/dir/etc.html失败不同端口 ( 81和80)
http://a.opq.com/dir/other.html失败不同域名 ( xyz和opq)

受浏览器同源策略的限制,非同源的脚本不能操作其他域的页面对象(如DOM等)

同源策略,它是由[Netscape]提出的一个著名的[安全策略]现在所有支持JavaScript 的浏览器都会使用这个策略。
所谓同源是指,域名,协议,端口相同。
当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面
当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,
即检查是否同源,只有和百度同源的脚本才会被执行。
如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以xyz.com下的js脚本采用ajax读取abc.com里面的文件数据是会被拒绝的。

不受同源策略限制的

1. 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。

2. 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="..."></script>,<img>,<link>,<iframe>等

什么是跨域?跨域有几种实现的形式?

跨域顾名思义就是突破同源策略的限制,去不同的域下访问数据。 主要有如下几种实现形式:

  • jsonp
  • CORS:跨域资源共享(Cross-Origin Resource Sharing)
  • 降域
  • postMessage()

 

原理:就是利用<script>标签没有跨域限制来达到与第三方通讯的目的。当需要通讯时,本站脚本创建一个<script>元素,地址指向第三方的API网址,形如: 
 <script src="http://www.example.net/api?param1=1&param2=2"></script> 
 并提供一个回调函数来接收数据(函数名可约定,或通过地址参数传递)。 
 第三方产生的响应为json数据的包装(故称之为jsonp,即json padding),形如: 
 callback({"name":"hax","gender":"Male"}) 
 这样浏览器会调用callback函数,并传递解析后json对象作为参数。本站脚本可在callback函数里处理所传入的数据。 

前端发请求时将函数名,例如callback传给后台,后台再返回这个函数,callback(info)。这样浏览器接受到数据后,会马上执行callback函数,就像回调函数一样。

前端代码:

function callback(info){
  alert(info);
}
function getUser(){
  let dom = document.createElement('script');
  dom.src = "http://127.0.0.1:3000/user?callback=callback";  //url上指明回调函数的名字
  document.body.appendChild(dom);
}

后台代码:

const express = require('express');
const app = express();
app.get('/user',(req,res)=>{
  let func = req.query.callback;
  res.send(func+'(' + '"success"'+')');
});
app.listen(3000,()=>{
  console.log('start');
});

再次执行getUser();成功弹出了success。这就是jsonP的原理。后台返回一个JS函数,带上参数,调用前端准备好的函数。即实现了发请求和回调函数的效果。

cors

这个就更简单了。上面ajax跨域的时候报了这个错误 No 'Access-Control-Allow-Origin' header is present on the requested resource。
那服务器返回的时候带上‘'Access-Control-Allow-Origin' 就好了。他的意思是允许哪些源的请求。你虽然不是我家的人,但这次我信任你,给你个钥匙。
改装下后台代码:

const express = require('express');
const app = express();
app.get('/user',(req,res)=>{
  res.header("Access-Control-Allow-Origin", "*");  //设置返回数据的头,代表接受任意源的请求
  res.send('success');
});
app.listen(3000,()=>{
  console.log('start');
});


再在控制台调用上文的getUserAjax()。成功。前端都不用改代码。不过要注意的是cors不是所有的浏览器都会支持。

 

CORS简介:
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

CORS的两种请求:

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。

(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
凡是不同时满足上面两个条件,就属于非简单请求。
浏览器对这两种请求的处理,是不一样的。
  1. 简单请求:对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

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

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

(1)Access-Control-Allow-Origin
         该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

(3)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字段的值。

上面说到,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

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

前端代码: 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>CORS</title>
    <style>
        .container{
             width:900px;
             margin:0 auto;
        }
    </style>
</head>
<body>
    <div class="container">
        <ul class="news">
            <li>头条新闻1</li>
            <li>头条新闻2</li>
            <li>头条新闻3</li>
            <li>头条新闻4</li>
        </ul>
        <button class="change">换一组</button>
    </div>

    <script>
        function $(id){
            return document.querySelector(id);
        }

        $('.change').addEventListener('click',function(){

            var xhr = new XMLHttpRequest();
            xhr.open('get','http://a.zyn.com:8080/getNews',true);
            //html通过zyn.com打开而通过ajax获取a.zyn.com上的资源,所以为跨域。
            xhr.send();
            xhr.onreadystatechange = function(){
                
                if(xhr.readyState ===4 && xhr.status ===200){
                    appendHtml(JSON.parse(xhr.responseText))
                }
            }
        })

        function appendHtml(news){

            var html ='';
            for(var i=0;i<news.length;i++){
                html +='<li>'+news[i]+'</li>'

            }
            $('.news').innerHTML = html;
        }
    </script>
</body>
</html>

后台代码:

app.get('/getNews',function(req,res){

    var news = [
        "头条新闻5",
        "头条新闻6",
        "头条新闻7",
        "头条新闻8",
        "头条新闻9",
        "头条新闻10",
        "头条新闻11",
        "头条新闻12"

    ]
    
     var data =[];
     for(var i=0;i<4;i++){

         var index = parseInt(Math.random()*news.length);
         data.push(news[index]);
         news.splice(index,1);
     }
     res.header("Access-Control-Allow-Origin","http://zyn.com:8080")//表示这个后台接受来自zyn.com的请求
     //res.header("Access-Control-Allow-Origin","*")//表示这个后台接受来自任意*的请求
     res.send(data)
    
})

 

jsonP和cors比较

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

降域

 

利用域名为同一个基础域名("http://a.zyn.com/a.html"和'"http://b.zyn.com/b.html")
使用document.domain="zyn.com"进行跨域;
这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,
否则无法利用document.domain进行跨域


比如,有两个二级域名:a.yang.com 和 b.yang.com,
可通过设定 document.domain 的值为主域名:yang.com 的方式,
突破浏览器的同源策略限制,来获取和操作对方的元素
  • 降域的使用

  • A 页面域为:a.yang.com

  • B 页面域为:b.yang.com

  • A 和 B 两页面都需加入该行代码:document.domain = 'yang.com'; ,‘yang.com 是 a.yang.com 和 b.yang.com 的主域名

postMessage

  • postMessage 是 HTML5 中新增方法,可实现跨域通信;

  • postMessage 并不是向服务器读写资源,只是向外发送消息而已;可以把它当做使用手机发送短信消息,仅此而已。

  • 也就是:A 页面向 B 页面发送了一条消息,B 页面会接受到该消息,如果 B 页面需要该消息,则监听 message;否则无需关心该消息

  • postMessage 的使用
    
    发送方:为目标元素添加事件处理程序,监听事件类型
    
    接收方:为 window 添加事件处理程序,事件类型为 message

    我们拿跨域中的iframe做例子: 

    <script type="text/javascript">
        window.parent.postMessage('hello world','*');    //在被嵌套的iframe的页面中写入这样一段代码
    </script>

    注意:postMessage的写法,postMessage之前写的是你要通信的window对象(也就是你要像谁通信),此时的window.parent的权限仅限于此,不能在像同域似的,进行获取父级的DOM元素,否则浏览器会报错,提示你不能进行跨域访问,我们再来看postMessage中所接收的参数,第一个参数就是你要像另外一个窗口传递的数据(只能传字符串类型),第二个参数表示目标窗口的源,协议+主机+端口号,是为了安全考虑,如果设置为“*”,则表示可以传递给任意窗口。

    那么另外一个窗口是如何接收数据的呢

    <script type="text/javascript">
        window.addEventListener('message',function(e){
            console.log(e.data);        //hello world
            console.log(e.origin);      //http://127.0.0.1:8020 所传来数据的域
    
        })
    </script>

    可以看到我们已经拿到了数据,那么拿到数据我们可以做那些操作呢

    <script type="text/javascript">
        window.addEventListener('message',function(e){
            console.log(e.data);        //hello world
                    if(e.data=="hello world"){
                           document.body.style.background = 'red';
                    }
            })
    </script>  

     

     

     


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值