写在后面
嗯。又来了,又说到布吕马了,这是两个可有可无的热门话题,从前我觉得这种基础该文没甚么好写的,会想著你去了解下层啊,并非很单纯吗。但最近在开发两个vscode 应用程序辨认出,当你刚进阶一样小东西的时候,你不能想这么多,因为你对他不熟识,当你碰到不能的小东西,你是想先找到软件系统,然后通过这个软件系统再去深入细致认知。就比如布吕马,后辈或者刚碰触的人对它并并非那么熟识,所以说列举许多他们累积的计划,以及许多常用的情景来给别人带来许多补救的路子,整件事是有意义的。(完稿之后还辨认出通心面。以后忘了还能回来看看)
只不过那时的环境对刚进阶的后端而言,非常的不亲善,一方面吧,很多刚后辈没经历过辅助工具的更改时代,另一方面架构的插值预览速度很快。在从前你可能将掌控三种常用的表现手法就好了。但那时webpack-dev-server、vue-cli、parcel,这些钢架都展开了几层PCB,对熟识的人可能将很单纯,但是对还未进阶的人而言,实在是两个Vellore,虽然用起来很方便快捷,但某一天碰到了难题,你对它不熟识,你就会不知道所错。(但别慌,非主流 cli 的布吕马形式我单厢说到)
讲着讲着有点偏移方向,可能将我讲的也并不一定是恰当的。上面瞄准自问自答。
责任编辑N43EI243SL「What-How-Why」的形式来展开传授。而在在 How (如何解决布吕马,将会提供副标题的 11 种计划。)
关键的说明: 在该文,web 端门牌号为 localhost:8000 服务器端门牌号为 localhost:8080,这一点希望你能读懂,会横跨概要,你也可以把该处的两边的门牌号消去你他们的门牌号。
以下所有标识符均在https:// github.com/hua19 95116/node-demo/tree/master/node-cors
一、布吕马是甚么?
1.相混路子
布吕马难题只不过是应用程序的相混路子所导致的。
相混路子是两个关键的安全路子,它用于限制两个origin 当布吕马时会收到以下错误
2.相混示例
那么如何才算是相混呢?先来看看 url 的组成部分
http://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
只有当
protocol(协议)、domain(域名)、port(端口)三者一致。
protocol(协议)、domain(域名)、port(端口)三者一致。
protocol(协议)、domain(域名)、port(端口)三者一致。
才是相混。
以下协议、域名、端口一致。
http://www.example.com:80/a.js
http://www.example.com:80/b.js
以下这种看上去再相似也没用,都不是相混。
http://www.example.com:8080
http://www2.example.com:80
在这里注意一下啊,这里是为了突出端口的区别才写上端口。在默认情况下 http 可以省略端口 80, https 省略 443。这别到时候闹笑话了,你和我说http://www.example.com:80 和http://www.example.com 并非相混,他俩是两个小东西。
http: //www.example.com:80===http://www.example.com
https://www.example.com:443 ===https://www.example.com
唔,还是要说明一下。
二、如何解决布吕马?
1.CORS
布吕马资源共享(CORS ) 是一种机制,它使用额外的HTTP 头来告诉应用程序 让运行在两个 origin (domain) 上的 Web 应用被准许访问来自不相混服务器上的指定的资源。当两个资源从与该资源本身所在的服务器不同的域、协议或端口请求两个资源时,资源会发起两个布吕马 HTTP 请求。
而在 cors 中会有单纯请求和复杂请求的概念。
应用程序支持情况
当你使用 IE<=9, Opera<12, or Firefox<3.5 或者更加老的应用程序,这个时候请使用 JSONP 。
a.单纯请求
不能触发CORS 预检请求 。这样的请求为“单纯请求”,请注意,该术语并不属于Fetch (其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“单纯请求”:
情况一: 使用以下方法(意思是以下请求意外的都是非单纯请求)
GET HEAD POST
情况二: 人为设置以下集合外的请求头
Accept Accept-Language Content-Language Content-Type (需要注意额外的限制)DPRDownlinkSave-DataViewport-WidthWidth情况三:Content-Type的值仅限于下列三者之一:(例如 application/json 为非单纯请求)
text/plainmultipart/form-dataapplication/x-www-form-urlencoded情况四:
请求中的任意XMLHttpRequestUpload 对象均没注册任何事件监听器;XMLHttpRequestUpload 对象可以使用XM LHttpRequest.upload属性访问。
情况五:
请求中没使用ReadableStream 对象。
b.非单纯请求
除以上情况外的。
c.Node 中的软件系统
原生形式
我们来看下后端部分的软件系统。Node中CORS的解决标识符.
app . use ( async ( ctx , next ) => {
ctx . set ( “Access-Control-Allow-Origin” , ctx . headers . origin );
ctx . set ( “Access-Control-Allow-Credentials” , true );
ctx . set ( “Access-Control-Request-Method” , “PUT,POST,GET,DELETE,OPTIONS” );
ctx . set (
“Access-Control-Allow-Headers” ,
“Origin, X-Requested-With, Content-Type, Accept, cc”
);
if ( ctx . method === “OPTIONS” ) {
ctx . status = 204 ;
return ;
}
await next ();
});
第三方中间件
为了方便快捷也可以直接使用中间件
const cors = require ( “koa-cors” );
app . use ( cors ());
关于 cors 的 cookie 难题
想要传递cookie需要满足 3 个条件
1.web 请求设置withCredentials
这里默认情况下在布吕马请求,应用程序是不带 cookie 的。但我们可以通过设置withCredentials来展开传递cookie.
// 原生 xml 的设置形式
var xhr = new XMLHttpRequest ();
xhr . withCredentials = true ;
// axios 设置形式
axios . defaults . withCredentials = true ;
2.Access-Control-Allow-Credentials为true
3.Access-Control-Allow-Origin为非*
这里请求的形式,在chrome中是能看到返回值的,但只要不满足以上其一,应用程序会报
Access to XMLHttpRequest at http://127.0.0.1:8080/api/corslist from origin http://127.0.0.1:8000 has been blocked by CORS policy: The value of the Access-Control-Allow-Credentials header in the response is which must be true when the requests credentials mode is include. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
Access to XMLHttpRequest at http://127.0.0.1:8080/api/corslist from origin http://127.0.0.1:8000 has been blocked by CORS policy: The value of the Access-Control-Allow-Origin header in the response must not be the wildcard * when the requests credentials mode is include. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
d.后端示例
分别演示一下后端部分单纯请求和非单纯请求
单纯请求
< script src = “https://cdn.bootcss.com/axios/0.19.2/axios.min.js” ></ script >
< script >
axios . get ( “http://127.0.0.1:8080/api/corslist” );
</ script >
非单纯请求
这里我们加入了两个非集合内的header头cc来达到非单纯请求的目的。
< script src = “https://cdn.bootcss.com/axios/0.19.2/axios.min.js” ></ script >
< script >
axios . get ( “http://127.0.0.1:8080/api/corslist” , { header : { cc : “xxx” } });
</ script >
小结
1、 在新版的 chrome 中,如果你发送了复杂请求,你却看不到options请求。可以在这里设置chrome://flags/#out-of-blink-cors设置成disbale,重启应用程序。对非单纯请求就能看到options请求了。
2、 一般情况下后端接口是不能开启这个布吕马头的,除非是许多与用户无关的不太关键的接口。
2.Node 正向代理
代理的路子为,利用服务器端请求不能布吕马的特性,让接口和当前站点同域。
代理前
代理后
这样,所有的资源以及请求都在两个域名下了。
a.cli 辅助工具中的代理
1) Webpack (4.x)
在webpack中可以配置proxy来快速获得接口代理的能力。
const path = require ( “path” );
const HtmlWebpackPlugin = require ( “html-webpack-plugin” );
module . exports = {
entry : {
index : “./index.js”
},
output : {
filename : “bundle.js” ,
path : path . resolve ( __dirname , “dist” )
},
devServer : {
port : 8000 ,
proxy : {
“/api” : {
target : “http://localhost:8080”
}
}
},
plugins : [
new HtmlWebpackPlugin ({
filename : “index.html” ,
template : “webpack.html”
})
]
};
修改后端接口请求形式,改为不带域名。(因为那时是同域请求了)
< button id = “getlist” > </ button >
< button id = “login” > 登录</ button >
< script src = “https://cdn.bootcss.com/axios/0.19.2/axios.min.js” ></ script >
< script >
axios . defaults . withCredentials = true ;
getlist . onclick = () => {
axios . get ( “/api/corslist” ). then ( res => {
console . log ( res . data );
});
};
login . onclick = () => {
axios . post ( “/api/login” );
};
</ script >
2) Vue-cli 2.x
// config/index.js
…
proxyTable : {
/api : {
target : http://localhost:8080 ,
}
},
…
3) Vue-cli 3.x
// vue.config.js 如果没就新建
module.exports = {
devServer: {
port: 8000,
proxy: {
“/api”: {
target: “http://localhost:8080”
}
}
}
};
4) Parcel (2.x)
// .proxyrc
{
“/api”: {
“target”: “http://localhost:8080”
}
}
看到这里,心里一句 xxx 就会脱口而出,一会写配置文件,一会 proxyTable ,一会 proxy,怎么这么多的形式?学不动了学不动了。。。不过也不用慌,还是有方法的。以上所有配置都是有着共同的下层包http-proxy-middleware .里面需要用到的各种websocket,rewrite等功能,直接看这个库的配置就可以了。关于 http-proxy-middleware 这个库的原理可以看我这篇该文https:// github.com/hua1995116/p roxy 当然了。。。对配置的位置入口还是非统一的。教两个搜索的技巧吧,上面配置写哪里都不用记的,想要哪个架构的 直接 google 搜索 xxx proxy 就行了。
例如 vue-cli 2 proxy 、 webpack proxy 等等….基本会搜到有官网的配置答案,通用且 nice。
b.使用他们的代理辅助工具
cors-anywhere
服务器端
// Listen on a specific host via the HOST environment variable var host = process . env . HOST || “0.0.0.0” ;
// Listen on a specific port via the PORT environment variable var port = process . env . PORT || 7777 ;
var cors_proxy = require ( “cors-anywhere” );
cors_proxy
. createServer ({
originWhitelist : [], // Allow all origins
requireHeader : [ “origin” , “x-requested-with” ],
removeHeaders : [ “cookie” , “cookie2” ]
})
. listen ( port , host , function () {
console . log ( “Running CORS Anywhere on “ + host + “:” + port );
});
后端标识符
< script src = “https://cdn.bootcss.com/axios/0.19.2/axios.min.js” ></ script >
< script >
axios . defaults . withCredentials = true ;
getlist . onclick = () => {
axios
. get ( “http://127.0.0.1:7777/http://127.0.0.1:8080/api/corslist” )
. then ( res => {
console . log ( res . data );
});
};
login . onclick = () => {
axios . post ( “http://127.0.0.1:7777/http://127.0.0.1:8080/api/login” );
};
</ script >
效果展示
缺点
无法专递 cookie,原因是因为这个是两个代理库,作者觉得中间传递 cookie 太不安全了。https:// github.com/Rob–W/cors- anywhere/issues/208#issuecomment-575254 153
c.charles
介绍
这是两个测试、开发的神器。介绍与使用
利用 charles 展开布吕马,本质是请求的拦截与代理。
在tools/map remote中设置代理
后端标识符
< button id = “getlist” > </ button >
< button id = “login” > 登录</ button >
< script src = “https://cdn.bootcss.com/axios/0.19.2/axios.min.js” ></ script >
< script >
axios . defaults . withCredentials = true ;
getlist . onclick = () => {
axios . get ( “/api/corslist” ). then ( res => {
console . log ( res . data );
});
};
login . onclick = () => {
axios . post ( “/api/login” );
};
</ script >
后端标识符
router . get ( “/api/corslist” , async ctx => {
ctx . body = {
data : [{ name : “秋风的笔记” }]
};
});
router . post ( “/api/login” , async ctx => {
ctx . cookies . set ( “token” , token , {
expires : new Date ( + new Date () + 1000 * 60 * 60 * 24 * 7 )
});
ctx . body = {
msg : “成功” ,
code : 0
};
});
效果
访问http:// localhost:8000/charles
完美解决。
唔。这里又有两个注意点。在Mac mojave 10.14中会出现charles抓不到本地包的情况。这个时候需要自定义两个域名,然后配置hosts指定到127.0.0.1。然后修改访问形式http://localhost.charlesproxy.com:8000/charles。
3.Nginx 反向代理
介绍
nginx 安装教程
配置下 hosts
127.0.0.1 local.test
配置 nginx
server {
listen 80;
server_name local.test;
location /api {
proxy_pass http://localhost:8080;
}
location / {
proxy_pass http://localhost:8000;
}
}
启动 nginx
sudo nginx
重启 nginx
sudo nginx -s reload
实现
后端标识符
< script >
axios . defaults . withCredentials = true ;
getlist . onclick = () => {
axios . get ( “/api/corslist” ). then ( res => {
console . log ( res . data );
});
};
login . onclick = () => {
axios . post ( “/api/login” );
};
</ script >
后端标识符
router . get ( “/api/corslist” , async ctx => {
ctx . body = {
data : [{ name : “秋风的笔记” }]
};
});
router . post ( “/api/login” , async ctx => {
ctx . cookies . set ( “token” , token , {
expires : new Date ( + new Date () + 1000 * 60 * 60 * 24 * 7 )
});
ctx . body = {
msg : “成功” ,
code : 0
};
});
效果
访问http://local.test/charles
应用程序显示
4.JSONP
JSONP主要是利用了script标签没布吕马限制的这个特性来完成的。
使用限制
仅支持 GET 方法,如果想使用完整的 REST 接口,请使用 CORS 或者其他代理形式。
流程解析
1.后端定义解析函数(例如 jsonpCallback=function(){….})
2.通过 params 形式包装请求参数,并且声明执行函数(例如 cb=jsonpCallback)
使用示例
后端实现
const Koa = require ( “koa” );
const fs = require ( “fs” );
const app = new Koa ();
app . use ( async ( ctx , next ) => {
if ( ctx . path === “/api/jsonp” ) {
const { cb , msg } = ctx . query ;
ctx . body = ` ${ cb } ( ${ JSON . stringify ({ msg } )})` ;
return ;
}
});
app . listen ( 8080 );
普通 js 示例
< script type = “text/javascript” >
window . jsonpCallback = function ( res ) {
console . log ( res );
};
</ script >
< script
src = “http://localhost:8080/api/jsonp?msg=hello&cb=jsonpCallback”
type = “text/javascript”
></ script >
JQuery Ajax 示例
< script src = “https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js” ></ script >
< script >
$ . ajax ({
url : “http://localhost:8080/api/jsonp” ,
dataType : “jsonp” ,
type : “get” ,
data : {
msg : “hello”
},
jsonp : “cb” ,
success : function ( data ) {
console . log ( data );
}
});
</ script >
原认知析
只不过这是 js 的魔法
我们先来看最单纯的 js 调用。嗯,很自然的调用。
< script >
window . jsonpCallback = function ( res ) {
console . log ( res );
};
</ script >
< script >
jsonpCallback ({ a : 1 });
</ script >
我们稍稍改造一下,外链的形式。
< script >
window . jsonpCallback = function ( res ) {
console . log ( res );
};
</ script >
< script src = “http://localhost:8080/api/a.js” ></ script > // http://localhost:8080/api/a.js jsonpCallback({a:1});
我们再改造一下,我们把这个外链的 js 就当做是两个动态的接口,因为本身资源和接口一样,是两个请求,也包含各种参数,也可以动态化返回。
< script >
window . jsonpCallback = function ( res ) {
console . log ( res );
};
</ script >
< script src = “http://localhost:8080/api/a.js?a=123&cb=sonpCallback” ></ script >
// http://localhost:8080/api/a.js jsonpCallback({a:123});
你仔细品,细细品,是并非 jsonp 有的优势是 script 加载 js 的优势,加载的形式只不过换了一种说法。这也告诉我们两个道理,很多小东西并没那么神奇,是在你所学的知识范围内。就好比,桃树和柳树,如果你把他们当成很大跨度的小东西去记忆认知,那么世上这么多树,你真的要累死了,你把他们都当成是树,哦吼?你会突然辨认出,你对世界上所有的树都有所了解,他们单厢长叶子,光合作用….当然也有个例,但你只需要去记忆这些细微的差别,抓住主干。。。嗯,反正就这么个道理。
5.Websocket
WebSocket 规范定义了一种 API,可在网络应用程序和服务器之间建立“套接字”连接。单纯地说:客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。详细教程可以看https://www. html5rocks.com/zh/tutor ials/websockets/basics/
这种形式本质没使用了 HTTP 的响应头, 因此也没跨域的限制,没甚么过多的解释直接上标识符吧。
后端部分
< script >
let socket = new WebSocket ( “ws://localhost:8080” );
socket . onopen = function () {
socket . send ( “秋风的笔记” );
};
socket . onmessage = function ( e ) {
console . log ( e . data );
};
</ script >
后端部分
const WebSocket = require ( “ws” );
const server = new WebSocket . Server ({ port : 8080 });
server . on ( “connection” , function ( socket ) {
socket . on ( “message” , function ( data ) {
socket . send ( data );
});
});
6.window.postMessage
window.postMessage()方法可以安全地实现跨源通信。通常,对两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数D ocument.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage()方法提供了一种受控机制来规避此限制,只要恰当的使用,这种方法就很安全。
用途
1.页面和其打开的新窗口的数据传递
2.多窗口之间消息传递
3.页面与嵌套的 iframe 消息传递
用法
详细用法看https:// developer.mozilla.org/z h-CN/docs/Web/API/Wi ndow/postMessage
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow: 其他窗口的两个引用,比如 iframe 的 contentWindow 属性、执行window.open 返回的窗口对象、或者是命名过或数值索引的window.frames 。message: 将要发送到其他 window 的数据。targetOrigin: 通过窗口的 origin 属性来指定哪些窗口能接收到消息事件.transfer(可选) : 是一串和 message 同时传递的Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权示例
index.html
< iframe
src = “http://localhost:8080”
frameborder = “0”
id = “iframe”
onload = “load()”
></ iframe >
< script >
function load () {
iframe . contentWindow . postMessage ( “秋风的笔记” , “http://localhost:8080” );
window . onmessage = e => {
console . log ( e . data );
};
}
</ script >
another.html
< div > hello</ div >
< script >
window . onmessage = e => {
console . log ( e . data ); // 秋风的笔记
e . source . postMessage ( e . data , e . origin );
};
</ script >
7.document.domain + Iframe
从第 7 种到第 9 种形式,我觉得别人的写的已经很好了,为了完整性,我就拿别人的了。如有雷同….(不对,是雷同….)不要说不出来。
该形式只能用于二级域名相同的情况下,比如a.test.com和b.test.com适用于该形式。 只需要给页面添加document.domain =test.com表示二级域名都相同就可以实现布吕马。
www. baidu. com .
三级域 二级域 顶级域 根域
// a.test.com
<body>
helloa
<iframesrc = “http://b.test.com/b.html”
frameborder = “0”
onload = “load()”
id = “frame” ></iframe>
<script>
document.domain= “test.com” ;
function load() {
console.log( frame.contentWindow.a) ;
} </script>
</body>
// b.test.com
<body>
hellob
<script>
document.domain= “test.com” ;
var a = 100;
</script>
</body>
8.window.location.hash + Iframe
实现原理
原理是通过 url 带 hash ,通过两个非布吕马的中间页面来传递数据。
实现流程
一开始 a.html 给 c.html 传两个 hash 值,然后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。 同样的,a.html 和 b.htm l 是同域的,都是http://localhost:8000,而 c.html 是http://localhost:8080
// a.html
< iframe src = “http://localhost:8080/hash/c.html#name1” ></ iframe >
< script >
console . log ( location . hash );
window . onhashchange = function () {
console . log ( location . hash );
};
</ script > // b.html< script >
window . parent . parent . location . hash = location . hash ;
</ script >
// c.html
< body ></ body >
< script >
console . log ( location . hash );
const iframe = document . createElement ( “iframe” );
iframe . src = “http://localhost:8000/hash/b.html#name2” ;
document . body . appendChild ( iframe );
</ script >
window 对象的 name 属性是两个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。
其中 a.html 和 b.html 是同域的,都是http://localhost:8000,而 c.html 是http://localhost:8080
// a.html
< iframe
src = “http://localhost:8080/name/c.html”
frameborder = “0”
onload = “load()”
id = “iframe”
></ iframe >
< script >
let first = true ;
// onload事件会触发2次,第1次加载布吕马页,并留存数据于window.name function load () {
if ( first ) {
// 第1次onload(布吕马页)成功后,切换到同域代理页面
iframe . src = “http://localhost:8000/name/b.html” ;
first = false ;
} else {
// 第2次onload(同域b.html页)成功后,读取同域window.name中数据
console . log ( iframe . contentWindow . name );
}
}
</ script >
b.html 为中间代理页,与 a.html 同域,内容为空。
// b.html
< div ></ div >
// c.html
< script >
window . name = “秋风的笔记” ;
</ script >
通过 iframe 的 src 属性由外域转向本地域,布吕马数据即由 iframe 的window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的布吕马访问限制,但同时它又是安全操作。
10.应用程序开启布吕马(毁灭者计划)
只不过讲下只不过布吕马难题是应用程序路子,源头是他,那么能否能关闭这个功能呢?
答案是肯定的。
注意事项: 因为应用程序是众多 web 页面入口。我们是否也可以像客户端那种,是用两个单独的专门宿主应用程序,来打开调试我们的开发页面。例如这里以 chrome canary 为例,这个是我专门调试页面的应用程序,不能用它来访问其他 web url。因此它也相对安全许多。当然这个形式,只限于当你真的被布吕马折磨地崩溃的时候才建议使用以下。使用后,请以正常的形式将他打开,以免你不小心用这个模式干了其他的事。
Windows
找到你安装的目录
.\Google\Chrome\Application\chrome.exe –disable-web-security –user-data-dir=xxxx
Mac
~/Downloads/chrome-data这个目录可以自定义.
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary –disable-web-security –user-data-dir=~/Downloads/chrome-data
效果展示
嗯,使用起来很香,但再次提醒,一般情况千万别轻易使用这个形式,这个形式好比七伤拳,使用的好威力无比,使用不好,很容易伤到他们。
三、为甚么需要布吕马?
在最一开始,我们知道了,布吕马只存在于应用程序端。而应用程序为 web 提供访问入口。我们在可以应用程序内打开很多页面。正是这样的开放形态,所以我们需要对他有所限制。就比如林子大了,甚么鸟都有,我们需要有两个统一的规范来展开约定才能保障这个安全性。
1.限制不相混的请求
这里还是用最常用的形式来传授,例如用户登录 a 网站,同时新开 tab 打开了 b 网站,如果不限制相混, b 可以像 a 网站发起任何请求,会让不法分子有机可趁。
2.限制 dom 操作
我举个例子吧, 你先登录下www.baidu.com ,然后访问我这个网址。
https:// zerolty.com/node-demo/i ndex.html
你会辨认出,这个和真实的百度一模一样,如果再把域名搞的相似许多,是并非很容易被骗,如果可以展开 dom 操作…那么大家的信息在这种钓鱼网站眼里都是一颗颗小白菜,等着被收割。
可以在 http 返回头 添加X-Frame-Options: SAMEORIGIN防止被别人添加至 iframe。写在最后
以上最常用的是前 4 种形式,特别是第 2 种非常常用,我里面也提到了多种示例,大家可以慢慢消化一下。希望未来有更加安全的形式来限制 web ,解决布吕马的头疼,哈哈哈哈。
有两个不成熟的想法,可以搞这么两个应用程序,只让访问内网/本地网络,专门给开发者用来调试页面用,对静态资源可以配置白名单,这样是并非就没布吕马难题了,23333。上述如有错误,请第一时间指出,我会展开修改,以免给大家来误导。
原作者姓名:蓝色的秋风
原出处:segmentfault
原文链接:10 种布吕马软件系统(附毁灭者计划)
资源下载 此资源下载价格为2.88 B,包年VIP免费,请先登录 2405474279