crossing_origin

利弊共存之Cross-Origin篇

1. cross-origin 介绍

什么是cross-origin?

在浏览器同源策略限制下,向 不同源(不同协议、不同域名或者不同端口) 发送XHR请求
浏览器认为该请求不受信任,禁止请求,具体表现为请求后不正常响应。

产生跨域时,http请求能实际访问到目标资源并且响应成功,但是由于浏览器的同源策略限制,导致响应被拦截,并在控制台提示,现象为:


不同源示例:

crossing_origin_第1张图片

特别注意两点:

第一、如果是协议和端口造成的跨域问题“前台”是无能为力的

第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上,通俗来说localhost和127.0.0.1不同源。
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。

同源策略提出目的及其限制?

同源策略的目的是保护用户的安全和隐私。它防止恶意网站通过跨域请求访问用户的敏感信息,或者利用其他网站上的漏洞进行攻击。

同源策略施加了以下限制:

  • JavaScript 只能访问与其来源相同的页面和代码。
  • DOM(文档对象模型)限制了对不同源文档的访问。
  • XMLHttpRequest 和 Fetch API 限制了对不同源的 AJAX 请求。

2. 如何解决cross-origin?

一 JSONP跨域通信
浏览器解析HTML文档,当解析到< script >元素的src属性时,会触发一个GET请求,而浏览器不检查src属性值的URL与网页服务器地址是否同源,利用这个漏洞(姑且认为是漏洞吧),服务端可以将JSON数据组装成JS文件流返回给网页。而只是简单的返回JSON数据还不行,需要网页一个已经加载到浏览器内存的JS函数callback(就是padding部分),用来接收JSON数据。浏览器接收到JS文件流,JS解释器执行解析,完成callback函数的执行。

前端伪代码:



    
        JSONP
    
    
        
        
    

后端伪代码:

public String getFun(String funName){
    return funName + '(' + JSON.toJsonString(data) + ')';
}
  • 注意:

    JSONP是通过< script >触发的GET请求,和ajax无半毛钱关系,JSONP请求数据的方式不能叫做 ajax,因为它没有使用xhr发送请求(xhr,即XMLHttpRequest实例化对象)。

二 web sockets

web sockets是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用)

web sockets原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。

只有在支持web socket协议的服务器上才能正常工作。

伪代码:

var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
    var data = event.data;
}
三 window.PostMessage(HTML5中的XMLHttpRequest Level 2中的API)

1) http://a.com/index.html中的代码:



2) http://b.com/index.html中的代码:


四 document.domain + Iframe (只有在主域相同的时候才能使用该方法)

1)在http://www.a.com/a.html中:

document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    //在这里操作doc,也就是b.html
    ifr.onload = null;
};

2)在http://www.script.a.com/b.html中:

document.domain = 'a.com'; 
五 window.location.hash + Iframe

原理是利用location.hash来进行传值。

假设域名http://a.com下的文件cs1.html要和http://cnblogs.com域名下的cs2.html传递信息。

1)cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向http://cnblogs.com域名下的cs2.html页面

2)cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据

3)同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一旦有变化则获取获取hash值

注:由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于http://a.com域名下的一个代理iframe

代码如下:

先是http://a.com下的文件cs1.html文件:

function startRequest(){
    var ifr = document.createElement('iframe');
    ifr.style.display = 'none';
    ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
    document.body.appendChild(ifr);
}
 
function checkHash() {
    try {
        var data = location.hash ? location.hash.substring(1) : '';
        if (console.log) {
            console.log('Now the data is '+data);
        }
    } catch(e) {};
}
setInterval(checkHash, 2000);

www.cnblogs.com域名下的cs2.html:

//模拟一个简单的参数处理操作
switch(location.hash){
    case '#paramdo':
        callBack();
        break;
    case '#paramset':
        //do something……
        break;
}
 
function callBack(){
    try {
        parent.location.hash = 'somedata';
    } catch (e) {
        // ie、chrome的安全机制无法修改parent.location.hash,
        // 所以要利用一个中间的cnblogs域下的代理iframe
        var ifrproxy = document.createElement('iframe');
        ifrproxy.style.display = 'none';
        ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';    // 注意该文件在"a.com"域下
        document.body.appendChild(ifrproxy);
    }
}

http://a.com下的域名cs3.html

//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
六 window.name + Iframe

window.name 的美妙之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

1)创建http://a.com/cs1.html

2)创建http://a.com/proxy.html,并加入如下代码

<head>
  <script>
  function proxy(url, func){
    var isFirst = true,
        ifr = document.createElement('iframe'),
        loadFunc = function(){
          if(isFirst){
            ifr.contentWindow.location = 'http://a.com/cs1.html';
            isFirst = false;
          }else{
            func(ifr.contentWindow.name);
            ifr.contentWindow.close();
            document.body.removeChild(ifr);
            ifr.src = '';
            ifr = null;
          }
        };
 
    ifr.src = url;
    ifr.style.display = 'none';
    if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc);
    else ifr.onload = loadFunc;
 
    document.body.appendChild(iframe);
  }
</script>
</head>
<body>
  <script>
    proxy('http://www.baidu.com/', function(data){
      console.log(data);
    });
  </script>
</body>

3 在http://b.com/cs1.html中包含:


七 Nginx反向代理

Nginx作为反向代理服务器,接收来自客户端的http请求,并将其转发到另一个或多个服务器上。
Nginx通过配置相应的代理规则,拦截匹配到的前缀代理到要跨域访问的实际地址上,配置相关如下:

server {
    listen 80;
    server_name poisson_server;
    location / {
        proxy_pass http://192.168.142.51:8080/;
    }
}
八 CORS
8.1 什么是CORS?
CORS全称Cross-Origin Resource Sharing,是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制。
8.2 CORS实现原理
只要服务器明确表示 允许 ,则校验通过。服务器明确表示 拒绝 或 没有表示,则校验 不通过。
8.3 CORS请求类型
8.3.1 简单请求
当请求同时满足以下条件时,浏览器会认为它是一个简单请求:

请求方法属于下面的一种:

get

post

head

请求头仅包含安全的字段,常见的安全字段如下:

Accept

Accept-Language

Content-Language

Content-Type

DPR

Downlink

Save-Data

Viewport-Width

Width

请求头如果包含Content-Type,仅限下面的值之一:

text/plain

multipart/form-data

application/x-www-form-urlencoded

交互规范:

当浏览器判定某个ajax跨域请求是简单请求时,会发生以下的事情:

  • 请求头中会自动添加Origin字段

  • 服务器响应头中应包含Access-Control-Allow-Origin该字段的值可以是:

    *:表示我很开放,什么人我都允许访问

    具体的源:比如http://my.com,表示我就允许你访问

8.3.2 需要预检的请求

浏览器校验ajax请求是非简单请求后,会向服务器发送预检请求,具体流程如下:

  1. 浏览器发送预检请求,询问服务器是否允许
  2. 服务器允许
  3. 浏览器发送真实请求
  4. 服务器完成真实的响应

  1. 服务器发送预检请求,询问服务器是否允许
OPTIONS /api/user HTTP/1.1
Host: crossdomain.com
...
Origin: http://my.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: a, b, content-type

预检请求有以下特征:

  • 请求方法为OPTIONS
  • 没有请求体
  • 请求头中包含:
    1. Origin: 请求的源,和简单请求的含义一致
    2. Access-Controll-Request-Method: 后续的真实请求将使用的方法
    3. Access-Controll-Request-Headers: 后续的真实请求会改动的请求头
  1. 服务器允许

服务器收到预检请求后,可以检查预检请求中包含的信息,如果允许这样的请求,需要响应下面的消息格式

HTTP/1.1 200 OK
Date: Tue, 21 Apr 2020 08:03:35 GMT
...
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400
...

预检请求响应头中的各属性含义:
  • Access-Control-Allow-Origin:和简单请求一样,表示允许的源
  • Access-Control-Allow-Methods:表示允许的后续真实的请求方法
  • Access-Control-Allow-Headers:表示允许改动的请求头
  • Access-Control-Max-Age:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求了

完成预检之后,后续的处理与简单请求一致

8.3.3 附带身份凭证的请求
在ajax请求头中附加cookie信息,服务器允许前提下会在响应头中添加Access-Control-Allow-Credentials: true进行明确告知,如果服务器未进行明确告知,浏览器仍将此次请求作为跨域拒绝处理

注意:对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为*。
8.4 基于CORS的springboot解决CS的五种方法

详情点击

九 修改浏览器安全配置(须谨慎)
默认Google浏览器

1、在桌面找到chrome 浏览器图标,右键,属性标签页中。

2、在目标输入框中加上 --disable-web-security即可。

解决方法对比

比较体 缺点 优点 适用场景
JSONP 1. 会打乱服务器的消息格式
2.只能完成GET请求
实现简单 适用于数据量较小,不需要特别保密的场景
web socket(s) 1. 需要服务端配合实现
2. 只能处理文本数据、不稳定
可以实现双向通信,实时更新 适用于聊天室、游戏等实时性较高的场景
nginx反向代理 需要配置服务器,增加了服务器端的压力 能够支持跨域请求、保护了 API 的安全性 用于解决前端直接访问跨域 API 的问题
适用于请求量较大、需要保密的场景。
CORS 需要服务端配合设置响应头、性能稍低
非简单请求下,有次预检的操作,增加访问次数
能够支持跨域请求、支持多种数据类型 能够支持跨域请求,同时支持多种数
适用于大部分的场景。
postMessage 需要进行安全性检查、可能存在 XSS 攻击风险 可以进行不同窗口、不同域名之间的数据传递 适用于嵌套页面、多窗口通信等场景
document.domain 只适用于主域名相同、子域名不同的情况下 实现简单、不需要浏览器支持 适用于主域名相同、子域名不同的情况下
iframe 存在安全风险、容易遭受 CSRF 攻击 能够实现跨域通信 适用于嵌套页面、跨域数据传输等场景

的安全性| 用于解决前端直接访问跨域 API 的问题
适用于请求量较大、需要保密的场景。 |
| CORS | 需要服务端配合设置响应头、性能稍低
非简单请求下,有次预检的操作,增加访问次数 |能够支持跨域请求、支持多种数据类型| 能够支持跨域请求,同时支持多种数
适用于大部分的场景。 |
| postMessage | 需要进行安全性检查、可能存在 XSS 攻击风险 |可以进行不同窗口、不同域名之间的数据传递| 适用于嵌套页面、多窗口通信等场景 |
| document.domain | 只适用于主域名相同、子域名不同的情况下 |实现简单、不需要浏览器支持| 适用于主域名相同、子域名不同的情况下 |
| iframe | 存在安全风险、容易遭受 CSRF 攻击 |能够实现跨域通信| 适用于嵌套页面、跨域数据传输等场景 |

注:CORS是官方推荐跨域解决方案,但可根据实际场景自定义解决方案

你可能感兴趣的:(java)