跨域的产生和解决

时间:Dec. 10, 2021 分类:

目录:

遇到的跨域问题

aiops.whysdomain.com通过js方式去请求tcsso.whysdomain.com进行登录认证,报错为CORS问题

概念介绍

CORS

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)

它允许浏览器向跨源服务器,发出XMLHttpRequest请求

CORS需要浏览器和服务器同时支持

目前,所有浏览器都支持该功能,IE浏览器不能低于IE10(2011年4月发布) 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

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

详见https://fetch.spec.whatwg.org/#http-cors-protocol

XMLHttpRequest

XMLHttpRequest是一个浏览器接口,使得Javascript可以进行HTTP(S)通信

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.baidu.com');
xhr.send();
xhr.status;
xhr.responseText;

同源协议

介绍

1995年,同源协议由Netscape公司引入浏览器。目前,所有浏览器都实行这个政策

同源协议要求

  • 协议相同
  • 域名相同
  • 端口相同

HTTP的URL通用格式

<acheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

对于刚才的aiops

http://aiops.whysdomain.com/#/source/tcdomain/domain/list/tcdomain-000004

目的

保证用户信息的安全,防止恶意的网站窃取数据。

设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?

很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。

由此可见,"同源协议"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了

限制范围

  1. Cookie、LocalStorage 和 IndexDB无法读取
  2. DOM无法获得
  3. Ajax请求不能发送
Set-Cookie: key=value; domain=.whysdomain.com; path=/

DOM

示例从iframe获取数据

document.getElementById("pageMainIframe").contentWindow.document

Ajax

ajax的功能

  • 不刷新页面更新网页
  • 在页面加载后从服务器请求数据
  • 在页面加载后从服务器接收数据
  • 在后台向服务器发送数据

只能发给同源的网址,否则就报错

有三种规避方式

  • Jsonp(jsonp只能发送GET请求)
  • Websocket
  • CORS

websocket不受同源限制是因为请求头中带着Origin源的标识可以自行判断

GET /chat HTTP/1.1
Host: tke-webshell.whysdomain.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://tke-webshell.whysdomain.com

所以cors是为了

  • 实现ajax的功能
  • 由服务端通过Origin确认是否允许访问

CORS原理

cors的请求分为两种

  • 简单请求
  • 非简单请求

简单请求的限制需要同时满足

请求方法是以下三种方法之一

  • HEAD
  • GET
  • POST

HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type为application/x-www-form-urlencoded、multipart/form-data和text/plain之一

简单请求主要目的就是兼容表单的一个提交

简单请求

浏览器直接发出CORS请求,会在Request Header之中,增加一个Origin字段

GET /login HTTP/1.1
Origin: http://aiops.whysdomain.com
Host: aiops.whysdomain.com
Connection: keep-alive
User-Agent: Mozilla/5.0...

Origin字段用来说明本次请求的源(协议 + 域名 + 端口)

服务端根据这个值,决定是否同意这次请求

  • 如果允许,需要在Responce Header中添加Access-Control-Allow-Origin字段
  • 如果不允许,就不添加即可

浏览器根据Access-Control-Allow-Origin来判断这次请求是否被允许(和HTTP状态码无关)

响应没有任何CORS相关的头信息字段,浏览器就会认定,服务端不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获

Access-Control-Allow-Origin: http://aiops.whysdomain.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin:服务端允许的源,可以为'*'允许任意源访问(必须)
  • Access-Control-Allow-Credentials:是否允许发送Cookie
  • Access-Control-Expose-Headers:允许发送的Header

对于Access-Control-Expose-Headers,进行CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定

非简单请求

浏览器会在进行请求前,先进行预检操作

示例一个PUT请求的预检报文

OPTIONS /cors HTTP/1.1
Origin: http://aiops.whysdomain.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: aiops.whysdomain.com
Connection: keep-alive
User-Agent: Mozilla/5.0...

使用的方法为OPTIONS,必需要包含两个子弹

  • Access-Control-Request-Method:浏览器的CORS请求会用到哪些HTTP方法
  • Access-Control-Request-Headers:浏览器CORS请求会额外发送的头信息字段,多个用逗号分隔

服务器会对预检操作进行响应

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2021 01:15:39 GMT
Access-Control-Allow-Origin: http://aiops.whysdomain.com
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
  • Access-Control-Allow-Origin:服务端允许的源,可以为'*'允许任意源访问(必须)
  • Access-Control-Allow-Headers:服务端允许发送的Header
  • Access-Control-Allow-Methods:服务端允许的方法
  • Access-Control-Allow-Credentials:是否允许发送Cookie
  • Access-Control-Max-Age:预检有效期,在有效期不需要发送预检

浏览器根据Access-Control-Allow-Origin等字段判断是否允许方法

响应没有任何CORS相关的头信息字段,浏览器就会认定,服务端不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获

如果允许就按照简单请求进行发送和验证即可

跨域问题的解决

通用解决方式

变为一个源

  1. nginx代理到不同服务
  2. 写到一个程序

cors访问

  1. 服务端提供跨域支持
  2. 浏览器关闭cors的校验

其他

  1. Jsonp
  2. Websocket

我们遇到的问题

aiops.whysdomain.com(源)
->tcsso.whysdomain.com(跨域)
->tccas.whysdomain.com(跨域)
->wcas.whysdomain.com(跨域)

Nginx

server {
        listen 80;
        server_name tcsso.whysdomain.com;

        location /tcsso {
                if ($request_method = 'OPTIONS') {
                    add_header 'Access-Control-Allow-Origin' '*' always;
                    add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE' always;
                    add_header 'Access-Control-Allow-Headers' '*' always;
                    add_header 'Access-Control-Max-Age' 1728000 always;
                    add_header 'Content-Length' 0;
                    add_header 'Content-Type' 'text/plain; charset=utf-8';
                    return 204;
                }

                if ($request_method ~* '(GET|POST|DELETE|PUT)') {
                    #add_header 'Access-Control-Allow-Origin' '*' always;
                    add_header 'Access-Control-Allow-Origin' $http_origin;
                    add_header 'Access-Control-Allow-Credentials' 'true';
                }
                proxy_pass http://10.210.40.53:8883;
        }
}

但是因为wcas.whysdomain.com域名不在我们,当时只配置了针对tccas.whysdomain.com源的跨域

转换为

aiops.whysdomain.com(源)
->tcsso.whysdomain.com(跨域),获取到tccas的Url
openNewWindow
tccas.whysdomain.com(源)
->wcas.whysdomain.com(跨域)