利用Electron开发拦截非授权网络访问的类绿坝浏览器的实践

1. 前言

有道是"每一个非主流的功能实现后面都有一个非主流的业务需求",最近有一个业务需求是提供一款具备类"绿坝"功能的,核心要素是让用户访问少数指定网站,但是不能访问其他互联网网站的浏览器,由于其余一些别的非性能要求不能用市面上的浏览器,只能够用electron开搞。

实现以后将利用Electron实现一个只能访问少数指定网站,拦截其他网络请求的浏览器的方法总结如下。

2. 实现方法

众所周知,Electron里面有一个Chromium内核,从原理上是可以直接作为浏览器应用的。所以要利用Electron实现选择性的网络请求放行/拦截,和浏览器是大同小异的,只不过由于算是定制化的浏览器,我们能操作的空间更多了,除了常规的通过Proxy Server,PAC来筛选网络请求,还能够通过以代码在Electron内每次触发window.open和navigate的时候进行拦截。

2.1 Proxy Server拦截网络请求

常规来说,浏览器是能够通过指定Proxy Server代理服务器和bypass list例外列表来让所有或部分网络请求都通过代理服务器去访问互联网的。

用Proxy Server来拦截用户网络请求是比较tricky的,就在于这种方法的核心是指定一个必然不能够生效 的代理服务器,比如http://localhost:13579(端口瞎编一个没有服务的),来拦截任意不应让用户可以访问的网站,并通过指定bypass list让可访问的网站能够例外,不通过这个事实并不存在的代理服务器,而直接连接互联网。

在Electron中实现如下:

javascript 复制代码
import { app } from "electron";
// 为app指定一个瞎编的proxy-server地址,从而拦截所有非预期的网络请求
app.commandLine.appendSwitch("proxy-server", "http://127.0.0.1:12580");
// 为用户可访问的互联网地址开通例外规则
app.commandLine.appendSwitch("proxy-bypass-list", "<local>;*foo.com,*.google.com");

如上方代码所示,则在该Electron应用程序中,除了符合<local>;*foo.com,*.google.com规则的网络请求,都会被导向一个事实并不存在的代理服务器,从而被拦截,变相达到了过滤用户网络请求的目的。

2.2 PAC实现规则代理

PAC是一种以JS编写的规则文件,不过理论上流通的现代浏览器已经不接受直接访问PAC文件方式(file://.....),而是要访问网络服务器上的pac文件,哪怕是localhost也行。

因此在electron中,能够以nodejs开启一个简单的HTTP服务器,只提供对这个pac文件的访问。

Electron中设置PAC文件代理的方式:

js 复制代码
import { app } from "electron";
// 为app指定一个网络服务器上的pac文件地址,形如:http://127.0.0.1:6666/proxy.pac
app.commandLine.appendSwitch("proxy-pac-url", config.proxy_pac_url);

简单pac文件写法,用js写的一个函数FindProxyForURL指定规则:

js 复制代码
function FindProxyForURL(url,host){
    // 符合规则,可访问
    if(localHostOrDomainIs(host,"qq.com")){
        return "DIRECT"    
    }
    // 其余不可访问
    return "PROXY localhost:12345"
}

2.3 Electron代码拦截

这种方法主要是在BrowserWindow上面为特定事件绑定处理函数,要注意的是对初始窗口的事件绑定不会继承到被window.open打开的新窗口上,因此每一个窗口都要绑定事件。

对一个BrowserWindow的跳转/打开新窗口的事件绑定如下:

js 复制代码
// 页面内跳转
currentWindow.webContents.on("will-navigate", function (event, url) {
    // checkUrl()是自定义的检查函数,校验不通过后调用event.preventDefault()则可阻止跳转
    if (!checkUrl(url, options)) {
        showWarningDialog();
        event.preventDefault();
    }
});

// 打开新窗口
currentWindow.webContents.setWindowOpenHandler((detail) => {
    let url = detail.url;
    // 校验通过可以访问
    if (checkUrl(url, options)) {
        return {
            action: "allow",
            overrideBrowserWindowOptions: {
                title: "browser window",
                minimizable: BrowserWindowConfig.minimizable,
                webPreferences: {
                    contextIsolation: true,
                    plugins: true,
                },
            },
            outlivesOpener: true,
        };
    }
    // 校验不通过,拒绝打开新窗口
    return { action: "deny" };
});

之所以用setWindowOpenHandler而不是再监听打开新窗口事件后new BrowserWindow()是由于可能有部分新窗口的创建不是GET方法的,在实践上setWindowOpenHandler容错率更高。

3. 小结

经过了我们这个基于Electron的自制"绿坝"的实际应用,我个人认为在拦截的效力及灵活性上是前述第三种方法,在Electron内代码实现的方式是效果最好的,比如上面代码里的checkUrl()方法,可以基于正则写黑名单和白名单,而且方便后期分发的时候做定制化配置,相关设置写在配置文件里不用改源码。

唉,还是成为了曾经自己最讨厌的人。

相关推荐
不可能片场1 天前
工程实践 日志文件名是北京日期 内容却是 UTC
前端·electron
不可能片场5 天前
工程实践 台账备份从全量复制改成增量日志
前端·electron
林恒smileZAZ5 天前
用 Vue 3 + Electron 做桌宠:从启动退出到拖拽、单击与鼠标穿透
vue.js·electron·计算机外设
非常好吃的糖6 天前
Windows 桌面端的语音唤醒:从「识别出文字」到「识别出唤醒词」
electron
Suprman7 天前
从 B/S 到 Electron:平面排版套料系统桌面版开发实战
javascript·平面·electron·排序算法
不可能片场9 天前
工程实践 一个缓存把选片从十分钟压到一分钟
前端·electron
lemon_sjdk10 天前
从一段真实的 Electron IM 前端代码,看高级前端的工程化修养
前端·electron·状态模式·im
优选资源分享11 天前
PicGo v3.0.3 新版 | 开源免费图床工具
javascript·笔记·electron
不可能片场11 天前
Electron 发布核验 别把没查到当成没发出去
前端·electron
flash俊杰11 天前
桌宠点击反应:把一坨 if 拆成「归一化 → 门控查表 → 执行 → 广播」
electron