建议收藏,使用WebAssembly保护前端JS核心代码实战

简介: 上个月我花了2天开发了一个全新的VSCode插件叫“我爱掘金”,让所有掘友可以化身为小蝌蚪,在VSCode里实时聊天。使用的是一个开源项目 workerman-todpole ,在原项目的基础上我做了大量的修改和优化。也曾试图添加一些房管功能,比如只有管理员可以使用大红色,只有管理员可以发光等等。

需求


要实现房管功能,我需要实现一个方法,来生成和激活高级命令,激活后则可以获得高级权限。显然,我不想被别人看到这段代码里我是如何生成命令和校验命令的。


我也不想放到服务器上做成一个后端接口,因为每次都去请求接口会对服务器产生压力。而websocket每次收到消息时都需要去验证这个高级命令


所以,我确定了两个核心诉求


  • 高级命令的生成和激活直接在前端完成,避免请求服务器。


  • 不能让人看到生成和激活的核心逻辑,否则就失去了意义。


一番调研之后,发现WebAssembly可以满足我的这两点需求,接下来就是实战了。


WebAssembly介绍


WebAssembly 有一套完整的语义,并且可以生成一种 .wasm 的二进制格式,体积小且加载快, 其目标就是充分发挥硬件能力以达到原生执行效率。


运行仍然是在浏览器里,但使用一种二进制格式的.wasm文件 ,这完全能满足我的需求,并且主流浏览器对WebAssembly的兼容性很好。


比如下面这段代码,大家可以复制粘贴到浏览器的调试窗口中感受一下


WebAssembly.compile(new Uint8Array(`
  00 61 73 6d  01 00 00 00  01 0c 02 60  02 7f 7f 01
  7f 60 01 7f  01 7f 03 03  02 00 01 07  10 02 03 61
  64 64 00 00  06 73 71 75  61 72 65 00  01 0a 13 02
  08 00 20 00  20 01 6a 0f  0b 08 00 20  00 20 00 6c
  0f 0b`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)).then(module => {
  const instance = new WebAssembly.Instance(module)
  const { add, square } = instance.exports
  console.log('2 + 4 =', add(2, 4))
  console.log('3^2 =', square(3))
  console.log('(2 + 5)^2 =', square(add(2 + 5)))
})


1650861271(1).png


显然,你无法轻易的得知那段Uint8Array里的算法是具体如何实现的...


但是要编写代码,并编译成.wasm文件可不算太简单,比如在本案中,我选择C++来编写代码,并通过emscripten工具链编译成.wasm文件。


emscripten

1650861319(1).png


安装 emscripten 环境


# Get the emsdk repo
git clone https://github.com/emscripten-core/emsdk.git
# Enter that directory
cd emsdk
# Fetch the latest version of the emsdk (not needed the first time you clone)
git pull
# Download and install the latest SDK tools.
./emsdk install latest
# Make the "latest" SDK "active" for the current user. (writes .emscripten file)
./emsdk activate latest
# Activate PATH and other environment variables in the current terminal
source ./emsdk_env.sh


环境安装好之后,我们立刻来写一段C语言,并编译成.wasm让它运行在浏览器里吧。


#include <stdio.h>
int main(int argc, char ** argv) {
    printf("掘友们好");
    printf("\n");
    printf("这是一段来自C语言的问候");
    printf("\n");
}


编译命令


emcc helloworld.c -s WASM=1 -o helloworld.html


编译完成后,记得起一个服务,比如live-server,因为我们需要读取本地的.wasm文件。这样我们就可以在浏览器里看到这段来自C语言的问候了


1650861370(1).png


但是坦白说,我并不认为WebAssembly可以减小代码量,比如这段简单的代码编译成.wasm后竟然需要12KB


1650861403(1).png


实战代码保护


比如原本我需要的JS逻辑如下,每天根据零点的时间戳转36进制得到一个6位长度的字符串(这个长度的输入体验还算友好),用户输入这个命令后即可激活高级功能。


蝌蚪池塘里的算法已变更,不要天真的以为我会爆料自己的算法哦:P


JS的写法

function verifyCommand(cmd){
  var t_cmd = (new Date(new Date().setHours(0, 0, 0, 0)).getTime()/1000).toString(36);
  if(cmd == t_cmd)return 1;
  else return 0;
}


JS在浏览器中是完全暴露的,在浏览器调试模式下,非常容易就可以知晓高级命令是如何生成的。


当然如文章开头所说的,我们也可以放到后端接口中去校验


PHP的写法


function MyEncode($var,$targetBit){    
    $dic = array(
            0 => '0', 1 => '1', 2 => '2', 3 => '3', 4 => '4', 5 => '5', 6 => '6', 7 => '7', 8 => '8', 9 => '9',
            10 => 'A', 11 => 'B', 12 => 'C', 13 => 'D', 14 => 'E', 15 => 'F', 16 => 'G', 17 => 'H', 18 => 'I',
            19 => 'J', 20 => 'K', 21 => 'L', 22 => 'M', 23 => 'N', 24 => 'O', 25 => 'P', 26 => 'Q', 27 => 'R',
            28 => 'S', 29 => 'T', 30 => 'U', 31 => 'V', 32 => 'W', 33 => 'X', 34 => 'Y', 35 => 'Z'
    );
    $yushu=bcmod($var,$targetBit);
    $shang=floor(bcdiv($var, $targetBit));
    if($shang==0){
        return  $dic[$yushu];
    }
    else {
        return MyEncode($shang,$targetBit).$dic[$yushu];
    }
}
date_default_timezone_set('PRC');
$time =strtotime(date("Y-m-d 00:00:00"));
$v_cmd = MyEncode(intval($time),36);
$cmd = strtoupper($_GET["cmd"]);
if($cmd==$v_cmd){
    echo 1;
}else{
    echo 0;
}


但是利用服务端的话,那每次校验都必须请求接口,无疑会对服务器造成压力。


现在我们用C语言来写一下,并最终编译为WebAssembly的.wasm文件


//获得今天零点的时间戳,秒
unsigned int getTodayZeroTime() 
{  
  //太长了,函数的实现请看源码
}  
//将数字转为指定进制,decimal为进制值
void f( long int x, char *p ,int decimal)
{
  //太长了,函数的实现请看源码
}
int verifyCommand(std::string cmd) {
  int t_time = getTodayZeroTime();
  char t_cmd[MAXN]="";
  f(t_time,t_cmd,36);
  if(strcmp(t_cmd, cmd.data()) == 0){
    return 1;
  }else{
    return 0;
  }
 }


JS调用.wasm里的C方法并传参


要想在JS中调用WebAssembly里的函数,我们在函数外面用一个extern "C"来包裹


extern "C" {
  int verifyCommand(std::string cmd) {
    int t_time = getTodayZeroTime();
    char t_cmd[MAXN]="";
    f(t_time,t_cmd,36);
    if(strcmp(t_cmd, cmd.data()) == 0){
      return 1;
    }else{
      return 0;
    }
  }
}


在编译.wasm时添加命令


emcc verify.cc -o verify.html -s EXPORTED_FUNCTIONS="['_verifyCommand']" -s EXPORTED_RUNTIME_METHODS='["ccall"]'


重点是这两个参数,注意下划线和引号


  • -s EXPORTED_FUNCTIONS="['_verifyCommand']"
  • -s EXPORTED_RUNTIME_METHODS='["ccall"]'


编译完成后我们就可以使用JS来调用verifyCommand这个C语言里的函数了


//第一个参数,函数名称
//第二个参数,函数返回值类型
//第三个参数,函数受参类型数组
//第四个参数,传递的参数数组
Module.ccall("verifyCommand",'number',['string'],['qmocg0']);
复制代码


以下是调试窗口截图,我特意输对和输错来看了看返回值


1650861506(1).png


现在我们就完成了这个高级命令的校验函数,直接在前端同步完成校验,无须请求后台接口异步获得校验结果。因为核心代码都在二进制的.wasm文件中,有心人也无法【轻松】的看到函数内的逻辑是如何实现的。为什么重点标记了一下【轻松】二字呢?


因为能编译,就能反编译...


本文就到这里把,各位小蝌蚪们池塘见,记得点赞收藏,早晚你会用到的!


相关文章
|
1天前
|
人工智能 程序员 UED
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
|
12天前
|
设计模式 数据安全/隐私保护
Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式
这篇文章介绍了在Next.js框架下,如何处理中后台管理系统中特殊页面(如登录页)不包裹根布局(RootLayout)的问题。作者指出Next.js的设计理念是通过布局的嵌套来创建复杂的页面结构,这虽然保持了代码的整洁和可维护性,但对于特殊页面来说,却造成了不必要的布局包裹。文章提出了一个解决方案,即通过判断页面的skipGlobalLayout属性来决定是否包含RootLayout,从而实现特殊页面不包裹根布局的目标。
66 33
|
2天前
|
存储 安全 API
Next.js 实战 (九):使用 next-auth 完成第三方身份登录验证
这篇文章介绍了next-auth,一个为Next.js设计的身份验证库,支持多种认证方式,如电子邮件和密码、OAuth2.0提供商(如Google、GitHub、Facebook等)以及自定义提供商。文章包含了如何配置Github Provider以及会话管理,并提到了适配器Adapters在next-auth中的作用。最后,文章强调了next-auth的强大功能值得进一步探索。
24 10
|
3天前
|
中间件 API
Next.js 实战 (八):使用 Lodash 打包构建产生的“坑”?
这篇文章介绍了作者在使用Nextjs15进行项目开发时遇到的部署问题。在部署过程中,作者遇到了打包构建时的一系列报错,报错内容涉及动态代码评估在Edge运行时不被允许等问题。经过一天的尝试和调整,作者最终删除了lodash-es库,并将radash的部分源码复制到本地,解决了打包报错的问题。文章最后提供了项目的线上预览地址,并欢迎读者留言讨论更好的解决方案。
17 10
|
2天前
|
人工智能 JavaScript 前端开发
一段 JavaScript 代码,集成网站AI语音助手
根据本教程,只需通过白屏化的界面操作,即可快速构建一个专属的AI智能体。
|
1月前
|
JavaScript 前端开发 测试技术
在 golang 中执行 javascript 代码的方案详解
本文介绍了在 Golang 中执行 JavaScript 代码的四种方法:使用 `otto` 和 `goja` 嵌入式 JavaScript 引擎、通过 `os/exec` 调用 Node.js 外部进程以及使用 WebView 嵌入浏览器。每种方法都有其适用场景,如嵌入简单脚本、运行复杂 Node.js 脚本或在桌面应用中显示 Web 内容。
103 15
在 golang 中执行 javascript 代码的方案详解
|
19天前
|
前端开发 API 开发者
Next.js 实战 (五):添加路由 Transition 过渡效果和 Loading 动画
这篇文章介绍了Framer Motion,一个为React设计的动画库,提供了声明式API处理动画和页面转换,适合创建响应式用户界面。文章包括首屏加载动画、路由加载Loading、路由进场和退场动画等主题,并提供了使用Framer Motion和next.js实现这些动画的示例代码。最后,文章总结了这些效果,并邀请读者探讨更好的实现方案。
|
17天前
|
JavaScript 前端开发 API
Next.js 实战 (六):如何实现文件本地上传
这篇文章介绍了在Next.js中如何实现文件上传到本地的方法。文章首先提到Next.js官方文档中没有提供文件上传的实例代码,因此开发者需要自行实现,通常有两种思路:使用Node.js原生上传或使用第三方插件如multer。接着,文章选择了使用Node.js原生上传的方式来讲解实现过程,包括如何通过哈希值命名文件、上传到指定目录以及如何分类文件夹。然后,文章展示了具体的实现步骤,包括编写代码来处理文件上传,并给出了代码示例。最后,文章通过一个效果演示说明了如何通过postman模拟上传文件,并展示了上传后的文件夹结构。
|
1月前
Next.js 实战 (二):搭建 Layouts 基础排版布局
本文介绍了作者在Next.js v15.x版本发布后,对一个旧项目的重构过程。文章详细说明了项目开发规范配置、UI组件库选择(最终选择了Ant-Design)、以及使用Ant Design的Layout组件实现中后台布局的方法。文末展示了布局的初步效果,并提供了GitHub仓库链接供读者参考学习。
Next.js 实战 (二):搭建 Layouts 基础排版布局
|
2月前
|
JavaScript 前端开发 Java
springboot解决js前端跨域问题,javascript跨域问题解决
本文介绍了如何在Spring Boot项目中编写Filter过滤器以处理跨域问题,并通过一个示例展示了使用JavaScript进行跨域请求的方法。首先,在Spring Boot应用中添加一个实现了`Filter`接口的类,设置响应头允许所有来源的跨域请求。接着,通过一个简单的HTML页面和jQuery发送AJAX请求到指定URL,验证跨域请求是否成功。文中还提供了请求成功的响应数据样例及请求效果截图。
springboot解决js前端跨域问题,javascript跨域问题解决