zoukankan      html  css  js  c++  java
  • 同源策略和跨域的解决方案

    什么是同源?

    所谓同源是指:域名协议端口 相同。

    检测以下地址和http://www.cnblog.com/ricolee是否同源:

    URL 结果 原因
    http://www.cnblog.com/ricolee 成功 域名、协议、端口相同
    https://www.cnblog.com/ricolee 失败 协议不同
    http://www.cnblog.com:8888/ricolee 失败 端口不同
    http://www.cnblog.cn/ricolee 失败 域名不同

    为什么制定同源策略?

    同源策略(Same origin policy)存在于浏览器端是一种约定,由Netscape(网景)提出,用来保护浏览器的数据安全。如果没有同源策略,A网站可以随意访问B网站的Cookie等信息是不安全的,现在所有支持JavaScript 的浏览器都会使用这个策略。

    同源策略有什么影响,哪些需要跨域操作?

    • 调用XMLHttpRequest有时候需要跨域,同源策略是禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
    • fetchAPI通过跨站点方式访问资源,网络字体,例如Bootstrap(通过CSS使用@font-face 跨域调用字体)。
    • 通过canvas标签,绘制图表和视频。
    • DOM操作,同源策略禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。

    跨域有风险吗?

    跨域请求和Ajax技术都会极大地提高页面的体验,但同时也会带来安全的隐患,其中最主要的隐患来自于CSRF(Cross-site request forgery)跨站请求伪造。

    image

    CSRF攻击的大致原理是:

    1. 用户通过浏览器,访问正常网站A(例如某银行),通过用户的身份认证(比如用户名/密码)成功A网站;
    2. 网站A产生Cookie信息并返回给用户的浏览器;
    3. 用户保持A网站页面登录状态,在同一浏览器中,打开一个新的TAB页访问恶意网站B;网站B接收到用户请求后,返回一些攻击性代码,请求A网站的资源(例如转账请求);
    4. 浏览器执行恶意代码,在用户不知情的情况下携带Cookie信息,向网站A发出请求。
    5. 网站A根据用户的Cookie信息核实用户身份(此时用户在A网站是已登录状态),A网站会处理该请求,导致来自网站B的恶意请求被执行。

    跨域请求出现的错误

    例如端口1080的网站请求1090的接口会出现如下错误提示:

    `Access to XMLHttpRequest at 'http://localhost:1090/S02CrossDomain/HunterByGet' from origin 'http://localhost:1080' 
    has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.`
    

    跨域资源共享(CORS)[推荐]

    CORS(Cross-origin resource sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通(需要客户端和服务端协同处理)

    CORS背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于IE10

    整个CORS通信过程,都是浏览器自动完成,不需要用户参与,对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

    因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信

    CORS浏览器支持情况

    image

    客户端需要做什么?

    基于上述的CSRF的风险,各主流的浏览器都会对动态的跨域请求进行特殊的验证处理。验证处理分为简单请求验证处理和预先请求验证处理。

    两种请求

    浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

    只要同时满足以下两大条件,就属于简单请求。

    请求方法是下列之一:

    • GET
    • HEAD
    • POST

    请求头中的Content-Type请求头的值是下列之一:

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

    凡是不同时满足上面两个条件,就属于非简单请求。

    浏览器对这两种请求的处理,是不一样的。

    简单请求

    基本流程

    简单请求时,浏览器会直接发送跨域请求,并在请求头中携带Origin header,表明这是一个跨域的请求。

    服务器端接到请求后,会根据自己的跨域规则,通过Access-Control-Allow-OriginAccess-Control-Allow-Methods响应头,来返回验证结果。
    如果验证成功,则会直接返回访问的资源内容。

    如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应:

    image

    浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200

    image

    一般错误控制台会有如下类似提示:
    image

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

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

    withCredentials 属性

    默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等)。通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。

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

    xhr.withCredentials = true;
    

    另一方面,如果服务器接收带凭据的请求,会用下面的HTTP头部来响应表示同意。
    Access-Control-Allow-Credentials: true
    服务器还可以在Preflight响应中发送这个HTTP头部,表示允许源发送带凭据的请求。

    image

    如果发送的是带凭据的请求,但服务器的响应中没有包含这个头,那么浏览器就不会把响应交给JavaScript(responseText中将是空字符串,size为0)。

    image

    注意,当withCredentials属性设置为true,需要response header中的'Access-Control-Allow-Origin'为一个确定的域名,而不能使用'*'这样的通配符。

    image

    非简单请求

    预检请求

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

    非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

    浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。浏览器发出的Preflighted requests是一个OPTION请求

    OPTIONS请求头部中会包含以下头部:

    • Origin:表示请求来自哪个源。
    • Access-Control-Request-Method必填,用来列出浏览器的CORS请求会用到哪些HTTP方法。
    • Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。

    预检请求的回应

    服务器收到"预检(OPTIONS)"请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,设置比如Access-Control-Allow-MethodAccess-Control-Allow-Headers头部与浏览器沟通来判断是否允许这个请求。

    image

    如果Preflighted requests验证通过,浏览器才会发送真正的跨域请求。

    image

    如果Preflighted requests验证失败,则会返回403状态,浏览器不会发送真正的跨域请求。

    image

    Console查看具体的验证失败原因

    image

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

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

    Request header 有哪些

    Origin

    头在跨域请求或预先请求中,标明发起跨域请求的源域名。

    Access-Control-Request-Method

    头用于表明跨域请求使用的实际HTTP方法

    Access-Control-Request-Headers

    用于在预先请求时,告知服务器要发起的跨域请求中会携带的请求头信息

    Response header 有哪些

    Access-Control-Allow-Origin

    头中携带了服务器端验证后的允许的跨域请求域名,可以是一个具体的域名或是一个*(表示任意域名)。简单请求时,浏览器会根据此响应头的内容决定是否给脚本返回相应内容,预先验证请求时,浏览器会根据此响应头决定是否发送实际的跨域请求。

    Access-Control-Expose-Headers

    头用于允许返回给跨域请求的响应头列表,在列表中的响应头的内容,才可以被浏览器访问。

    Access-Control-Max-Age

    用于告知浏览器可以将预先检查请求返回结果缓存的时间,在缓存有效期内,浏览器会使用缓存的预先检查结果判断是否发送跨域请求。

    Access-Control-Allow-Credentials

    用于告知浏览器当withCredentials属性设置为true时,是否可以显示跨域请求返回的内容。简单请求时,浏览器会根据此响应头决定是否显示响应的内容。预先验证请求时,浏览器会根据此响应头决定在发送实际跨域请求时,是否携带认证信息。

    它的值是一个布尔值,表示是否允许发送Cookie.默认情况下,不发生Cookie,即:false。对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true。如果服务器不要浏览器发送Cookie,删除该字段即可。

    Access-Control-Allow-Methods(必须)

    用于告知浏览器可以在实际发送跨域请求时,可以支持的请求方法,可以是一个具体的方法列表或是一个*(表示任意方法)。简单请求时,浏览器会根据此响应头的内容决定是否给脚本返回相应内容,预先验证请求时,浏览器会根据此响应头决定是否发送实际的跨域请求。

    一个逗号分隔的列表,表明服务器支持的请求类型,比如:GET, POST

    Access-Control-Allow-Headers

    用于告知浏览器可以在实际发送跨域请求时,可以支持的请求头,可以是一个具体的请求头列表或是一个*(表示任意请求头)。简单请求时,浏览器会根据此响应头的内容决定是否给脚本返回相应内容,预先验证请求时,浏览器会根据此响应头决定是否发送实际的跨域请求。

    提供一个逗号分隔的列表表示服务器支持的请求数据类型。假如你使用自定义头部,比如:x-authentication-token 服务器需要在返回OPTIONS请求时,要把这个值放到这个头部里,否则请求会被阻止。

    服务端需要做什么?

    服务器端对于跨域请求的处理流程如下:

    首先查看http头部有无origin字段;
    如果没有,或者不允许,直接当成普通请求处理,结束;
    如果有并且是允许的,那么再看是否是preflight(method=OPTIONS);
    如果不是preflight(简单请求),就返回Allow-Origin、Allow-Credentials等,并返回正常内容。
    如果是preflight(预先请求),就返回Allow-Headers、Allow-Methods等,内容为空;

    .NET 后端实现CORS 一

    在web.config的<system.webServer>节点下加上以下配置(作用于整个网站):

    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE" />
      </customHeaders>
    </httpProtocol>
    

    .NET 后端实现CORS 二

    或者在代码中加上如下代码(只作用于当前方法):

    public ActionResult HunterAddHeadByCode()
    {
        // * 表示允许任何域名跨域访问
        Response.Headers.Add("Access-Control-Allow-Origin", "*");
        Response.Headers.Add("Access-Control-Allow-Headers", "*");
        Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
      
        var json = JsonConvert.SerializeObject(Hunters);
        return Json(json, JsonRequestBehavior.AllowGet);
    }
    

    .NET 后端实现CORS 三

    更好的方式觉得是通过特性实现,哪个接口需要标记特性即可。

    待续......
    

    nginx上的CORS配置

    location / {
         if ($request method = 'OPTIONS') { 
             add_ header ' Access -Control- Allow-0rigin' *';
             add_ header ” Access Control- Allow-Methods' 'GET, POST, OPTIONS' ;
             add header Access Control Max-Age ' 86400;6 add header ' Content-Type’” text/plain' ;
             add header ' Content-Length’0;
             return 204;
        }
        if ($request_ method = 'GET') {
             add_ header ' Access -Control-Allow-0rigin' 本';
             add header ' Access-Control-Al low-Methods' 'GET, POST, OPTIONS' ;
             add_ header' Access Control -Allow-Headers”'User-Agent , X- Requested -With , Cache - Control , Content -Type;
        }
    }
    

    优点

    1. CORS 通信与同源的 AJAX 通信没有差别,代码完全一样,容易维护。
    2. 支持所有类型的 HTTP 请求。

    缺点

    1. 存在兼容性问题,特别是 IE10 以下的浏览器。
    2. 第一次发送非简单请求时会多一次请求。

    jsonp 跨域

    前端实现

    Ajax请求加参数dataType: "jsonp"。如需指定特定回调函数就配置jsonpCallback参数。

    注意
    回调的函数要在window作用域内,否则调用不到。

    $.ajax({
            type: "get",
            async: false,
            dataType: "jsonp", //指定服务器返回的数据类型,
            //jsonpCallback: "showData",  //指定回调函数名称
            url: 'http://localhost:1090/S02CrossDomain/HunterByJsonp',
            success: function (res) {
                console.log('success');
                var result = JSON.stringify(res);
                $("#result").html(result);
               
            },
            error: function (data, textStatus, jqXHR) {
                console.log(data);
                $('#result').html(data.statusText);
            }
        });
    

    服务器端实现

    服务器端返回值也需要做些修改

    public ActionResult HunterByJsonp()
    {
        var callback = Request.Params["callback"].ToString();
        Response.ContentType = "application/json;charset=utf-8";
        var json = JsonConvert.SerializeObject(Hunters);
        var result = callback + "(" + json + ")";
        //注:不能用json返回,会报错
        //return Json(result, JsonRequestBehavior.AllowGet);
        return Content(result);
    }
    

    提示
    ASP.NET MVC 中不能用Json返回否则会报类似错误:jQuery33105546587291303868_1542953995969 was not called

    原理解析

    页面虽然不允许发起跨域的ajax请求,但引用不同域名的js脚本是可行的。

    1. 执行跨域的ajax请求时会自动发起一个Script请求,请求文件名为callback=jQueryxxx,jQueryxxx是jquery随机生成的一个回调函数名称。
    2. 该次请求返回来的结果则是jQueryxxx()函数调用字符串,执行这个函数完成跨域请求。

    一句话来概括就是,通过动态创建script标签,然后利用 src 属性进行跨域,而每一次跨域就是一个script脚本的引入。

    下图可以看到下图执行jsonp请求后引用了另一个域的script文件,每次跨域就引用一次:

    image

    下图为浏览器端收到返回值,执行返回的数据完成跨域操作

    image

    优点

    • 使用简便,没有兼容性问题

    **缺点 **

    • 只支持 GET 请求。
    • 由于是从其它域中加载代码执行,因此如果其他域不安全,很可能会在响应中夹带一些恶意代码。
    • 要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给 script 标签新增了一个 onerror 事件处理程序,但是存在兼容性问题。(未验证过)

    服务器代理

    服务器端是没有跨域限制的,由服务器端请求所需资源再返回客户端。

    参考

  • 相关阅读:
    Could not resolve com.android.support:appcompat-v7:28.0.0 错误处理
    解决 Could not resolve com.android.tools.build:gradle:3.1.3
    https://maven.google.com 连接不上的解决办法(转)
    jquery操作select(取值,设置选中)
    django 使用 request 获取浏览器发送的参数
    jquery下载,实时更新jquery1.2到最新3.3.1所有版本下载
    myeclipse 8.5反编译插件失效
    再探java基础——对面向对象的理解(2)
    庖丁解牛FPPopover
    去大连
  • 原文地址:https://www.cnblogs.com/ricolee/p/crossdomain.html
Copyright © 2011-2022 走看看