NodeJs 源码保护

        现在 NodeJs 开发 Server 端越来越流行,如果 Server 部署在自己公司的服务器上,那么可以认为环境是相对安全的,不需要做源码保护。但是如果需要在客户方部署,又不希望自己的源码暴露的时候,这个时候就需要源码保护。一般的源码保护方式就是 js 压缩/混淆之类的操作,增加 js 代码的不可读性,或者说是增加破解难度。

        本文讨论另一种使用字节码编译 nodejs 代码来保护源码的方式。

        NodeJs 使用 google 的 V8 引擎进行编译,具体可以参考  https://zhuanlan.zhihu.com/p/28590489,同时,我们利用 bytenode 这个插件来辅助生成字节码文件,具体请参考 https://github.com/OsamaAbbas/bytenode。

        项目构成:使用 express 创建一个项目,项目目录如下:

        NodeJs 源码保护_第1张图片

        主要文件及说明:

        bin/www:程序主入口

        routes/:路由 js 文件

        services/:核心业务逻辑处理的 js 文件

        app.js:NodeJs Server 启动入口。

        compile.js:字节码编译 js 的文件,后面会讲到。

        其余的文件不重要,也不会被编译成字节码。

        核心思路就是将关键的 js 代码编译成字节码,以保护我们的业务处理逻辑或者算法。

1. 安装依赖

npm install bytenode --save

2. compile.js,主要逻辑就是将项目代码拷贝到 dist 目录中,遍历 dist 下 routes 和 services 等核心 js 文件目录,使用 bytenode 插件将所有的 js 转换成 jsc 字节码文件,然后删除 js 源文件。

var bytenode = require('bytenode');
var fs = require('fs');
var path = require("path");

fs.exists('./dist', exist => {
    if (exist) {
        delDir('./dist');
    }
    fs.mkdirSync('./dist');
})

// 拷贝目录到 dist 下
fs.readdir('./', (err, files) => {
    if (err) {
        console.error(err);
        return;
    }
    for (var i = 0; i < files.length; i++) {
        var stat = fs.statSync('./' + files[i]);
        if (stat.isFile()) {
            if (files[i].indexOf('compile.js') == -1) {
                fs.writeFileSync('./dist/' + files[i], fs.readFileSync('./' + files[i]));
            }
        } else if (stat.isDirectory() && files[i].indexOf('dist') == -1) {
            createDocs('./' + files[i], './dist/' + files[i], function () {

            })
        } else {

        }
    }

    compileFile()
})

function compileFile() {
    // 编译 app.js 为字节码
    bytenode.compileFile({
        filename: './dist/app.js'
    });
    fs.unlinkSync('./dist/app.js');

    // 编译 filters/routes/services 目录下的js文件为字节码
    compileDir('./dist/filters');
    compileDir('./dist/routes');
    compileDir('./dist/services');
}

function compileDir(dir) {
    var stat = fs.statSync(dir);
    if (stat.isFile() && dir.indexOf('.js') != -1) {
        // 文件,直接转换
        bytenode.compileFile({
            filename: dir
        });
        fs.unlinkSync(dir);
    } else if (stat.isDirectory()) {
        // 目录,列出文件列表,循环处理
        var files = fs.readdirSync(dir);
        for (var i = 0; i < files.length; i++) {
            var file = dir + '/' + files[i];
            compileDir(file);
        }
    } else {

    }
}

//递归创建目录 同步方法  
function mkdirsSync(dirname) {
    if (fs.existsSync(dirname)) {
        return true;
    } else {
        if (mkdirsSync(path.dirname(dirname))) {
            console.log("mkdirsSync = " + dirname);
            fs.mkdirSync(dirname);
            return true;
        }
    }
}

function _copy(src, dist) {
    var paths = fs.readdirSync(src)
    paths.forEach(function (p) {
        var _src = src + '/' + p;
        var _dist = dist + '/' + p;
        var stat = fs.statSync(_src)
        if (stat.isFile()) {// 判断是文件还是目录
            fs.writeFileSync(_dist, fs.readFileSync(_src));
        } else if (stat.isDirectory()) {
            copyDir(_src, _dist)// 当是目录是,递归复制
        }
    })
}

/*
 * 复制目录、子目录,及其中的文件
 * @param src {String} 要复制的目录
 * @param dist {String} 复制到目标目录
 */
function copyDir(src, dist) {
    var b = fs.existsSync(dist)
    console.log("dist = " + dist)
    if (!b) {
        console.log("mk dist = ", dist)
        mkdirsSync(dist);//创建目录
    }
    console.log("_copy start")
    _copy(src, dist);
}

function createDocs(src, dist, callback) {
    console.log("createDocs...")
    copyDir(src, dist);
    console.log("copyDir finish exec callback")
    if (callback) {
        callback();
    }
}

function delDir(path) {
    let files = [];
    if (fs.existsSync(path)) {
        files = fs.readdirSync(path);
        files.forEach((file, index) => {
            let curPath = path + "/" + file;
            if (fs.statSync(curPath).isDirectory()) {
                delDir(curPath); //递归删除文件夹
            } else {
                fs.unlinkSync(curPath); //删除文件
            }
        });
        fs.rmdirSync(path);
    }
}

3. 修改 bin/www 文件,在最开始 引入 bytenode

require('bytenode');
var app = require('../app');
var debug = require('debug')('esreader-server:server');
var http = require('http');

...

4. 执行指令打包编译。

node compile.js

编译完成之后, dist 下面的所有文件即可作为发布到第三方服务器上的server。

这样做完之后,项目的启动,或者使用诸如 pm2 等工具来管理 server 时,都与之前的固有做法一致,不需要特殊处理。

 

 

你可能感兴趣的:(NodeJS)