跨域分析以及通解
Posted yerikyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跨域分析以及通解相关的知识,希望对你有一定的参考价值。
现今绝大多数新上线的网站都是基于前后端分离的部署模式来对外提供服务,而这种模式在不熟悉的情况下就很容易遇到一个恶心的问题——跨域
跨域形成原因
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里的跨域是广义的。
广义的跨域包括:
- 资源跳转: 链接,重定向,表单提交
- 资源嵌入:
<link>
,<script>
,<img>
,<iframe>
等DOM
标签 - 脚本请求: javascript 发起的 Ajax 请求等
而我们常说的跨域是狭义的,是由浏览器同源策略引起的一类请求场景。
同源策略
同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的js脚本和另外一个域的内容进行交互,如果缺少了同源策略,浏览器很容易受到来自XSS、CSFR等方面的入侵方式。所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。
我们可以这么理解一个url是由:协议、域名、端口 三部分组成。(一般端口默认80)
如:https://blog.yerikshu.lab:18822
所谓同源策略简单来说即要求同协议,同域名,同端口,三个一致。
例如:
- A: https://blog.yerikshu.lab:8080/index.html
- B: http://blog.yerikshu.lab:8080/index.html
- C: https://blog.yerikshu.lab:8080/index.html
- D: https://blog.yerikshu.lab:8081/index.html
- E: https://blog.yerikshu.lab:8080/test.html
A与B则是不同协议,A与C不同域名,A与D不同端口这些都是跨域。注意:http://localhost:8080与http://127.0.0.1:8080也是跨域的。
由于同源策略限制的内容还包括
- cookie、localStorage、indexDB无法读取
- DOM无法获取
- AJAX不能发送
解决方式
jsonp绕过浏览器的同源策略,通过websocket/cors
,正反代理来告诉服务端发起请求的源,由服务端来判断是否同意该请求。
常见的解决方式
- 最简单的方式就是将前后端都部署在同一台机器上面,系统上面解析成localhost,或许有些人会拍桌子了:本来开发初衷就是前后端分离,现在又合在一起部署,单点故障怎么办,一台机子挂了,前后端一起死。确实,但这种方式在古时候确实很方便啊,也没有所谓的跨域问题不是嘛
- 基于k8s进行发布,将前后端都放置在同一个service里面,通过不同的路由进行访问是不是也可以变相的认为是在同一台主机,这个其实也是一种绕过的方式,借助k8s的能力让web服务看起来是在同一台主机上面部署服务的同时具备高可用的特性
对于前端来说,可以做些什么?
- 一级域名相同,二级域名不同的情况下,可以设置
document.domain
相同,就可以共享cookie
- 以
iframe
和window.open
方法打开的窗口为例,有三种方法可以跨域:
- rl后#片段识别符携带传递参数 通过
hashchange
方法进行通知 - code>window.name不论同源只要在同一个窗口设置了这个属性就可以传参,容量大,但是需要额外监听
- code>window.postMessage 是H5引入的新的API,
window.postMessage
(data,target地址等) - 过上面的方法也可以读写其他窗口的localStorage
AJAX跨域
jsonp的原理就是利用<script>
标签没有跨域限制,通过<script>
标签src
属性,发送带有callback
参数的GET请求,服务端将接口返回数据拼凑到callback
函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback
函数返回的数据。
这种方案的优点是是简单适用、支持所有的浏览器,对服务端改动非常小,缺点是只能发送get请求,而且必须设置回调函数是因为作为一个scripts标签获取的js脚本是需要被执行的,如果是纯数据的话无法执行会报错
<script>
var script = document.createElement(script);
script.type = text/javascript;
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = http://www.yerik.lab:8080/login?user=yerikshu&callback=handleCallback;
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res)
alert(JSON.stringify(res));
</script>
服务端返回如下(返回时即执行全局函数):
handleCallback("success": true, "user": "yerikshu")
jquery Ajax实现
$.ajax(
url: http://www.domain2.com:8080/login,
type: get,
dataType: jsonp, // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data:
);
Vue axios实现
this.$http = axios;
this.$http.jsonp(http://www.domain2.com:8080/login,
params: ,
jsonp: handleCallback
).then((res) =>
console.log(res);
)
node.js实现
var querystring = require(querystring);
var http = require(http);
var server = http.createServer();
server.on(request, function(req, res)
var params = querystring.parse(req.url.split(?)[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, Content-Type: text/javascript );
res.write(fn + ( + JSON.stringify(params) + ));
res.end();
);
server.listen(8080);
console.log(Server is running at port 8080...);
webSocket
是一种通信协议,不实行同源策略,在请求头中有origin属性标记了请求源,缺点是需要支持webscoket的服务器才支持。
跨域资源共享(CORS)
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器要求不能低于IE10。
CORS 整个通信过程 都是浏览器自动完成的,不需要用户进行参与,当浏览器发现XMLHTTPRequest或原生fetch请求,会自动附加一些头信息,有时会进行一次附件的预检请求。
浏览器将CORS跨域请求分为简单请求和非简单请求。
只要同时满足一下两个条件,就属于简单请求
- 使用下列方法之一:
- head
- get
- post
- 请求的Heder是
- Accept
- Accept-Language
- Content-Language
- Content-Type: 只限于三个值:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
不同时满足上面的两个条件,就属于非简单请求。浏览器对这两种的处理,是不一样的。
简单请求
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin
字段。
GET /cors HTTP/1.1
Origin: http://api.yerik.lab
Host: api.yerik.lab
Accept-Language: zh-CN
Connection: keep-alive
User-Agent: Mozilla/5.0...
上面的头信息中,Origin
字段用来说明,本次请求来自哪个源(协议+域名+端口)。服务器根据这个值,决定是否同意这次请求。
CORS请求设置的响应头字段,都以Access-Control-
开头:
-
Access-Control-Allow-Origin
:必填
- 它的值要么是请求时
Origin
字段的值,要么是一个*,表示接受任意域名的请求。
-
Access-Control-Allow-Credentials
:可选
- 它的值是一个布尔值,表示是否允许发送
Cookie
。默认情况下,Cookie
不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie
可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie
,删除该字段即可。
-
Access-Control-Expose-Headers
:可选
- CORS请求时,
XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。比如,getResponseHeader(user)
可以返回User
字段的值。
非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
,亦或者是在Cookie设置特殊请求头比如X-Auth-Token
这种根据业务需要自定义的参数内容
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
预检请求
预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。请求头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,"预检"请求的头信息包括两个特殊字段。
-
Access-Control-Request-Method
:必选 - 用来列出浏览器的CORS请求会用到哪些HTTP方法,例子中是POST。
-
Access-Control-Request-Headers
:可选 - 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,例子中是
x-goog-authuser
。
:authority: play.yerikshu.lab
:method: OPTIONS
:path: /log?format=json&hasfast=true&authuser=0
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
access-control-request-headers: authorization,x-goog-authuser
access-control-request-method: POST
origin: https://www.play.yerikshu.lab
referer: https://www.play.yerikshu.lab/
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36 Edg/101.0.1210.32
预检请求的回应
服务器收到"预检"请求以后,检查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应。
HTTP回应中,除了关键的是Access-Control-Allow-Origin
字段,其他CORS相关字段如下:
-
Access-Control-Allow-Methods
:必选 - 它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
-
Access-Control-Allow-Headers
- 如果浏览器请求包括
Access-Control-Request-Headers
字段,则Access-Control-Allow-Headers
字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。 -
Access-Control-Allow-Credentials
:可选 - 该字段与简单请求时的含义相同。
-
Access-Control-Max-Age
:可选 - 用来指定本次预检请求的有效期,单位为秒。
CORS跨域示例
前端设置:
原生ajax:
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open(post, http://www.play.yerik.lab/login, true);
xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded);
xhr.send(user=admin);
xhr.onreadystatechange = function()
if (xhr.readyState == 4 && xhr.status == 200)
alert(xhr.responseText);
;
jquery ajax:
$.ajax(
xhrFields:
withCredentials: true // 前端设置是否带cookie
,
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
);
服务端设置:
nodejs代码
var http = require(http);
var server = http.createServer();
var qs = require(querystring);
跨域 cookies