整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

禁止别人调试自己的前端页面代码,这几种方法很行!

禁止别人调试自己的前端页面代码,这几种方法很行!

为啥要禁止?

  • 由于前端页面会调用很多接口,有些接口会被别人爬虫分析,破解后获取数据
  • 为了 杜绝 这种情况,最简单的方法就是禁止人家调试自己的前端代码

无限 debugger

  • 前端页面防止调试的方法主要是通过不断 debugger 来疯狂输出断点,因为 debugger 在控制台被打开的时候就会执行
  • 由于程序被 debugger 阻止,所以无法进行断点调试,所以网页的请求也是看不到的
  • 基础代码如下:
/**
* 基础禁止调试代码
*/
(()=> {
	function ban() {
	  setInterval(()=> {
	    debugger;
	  }, 50);
	}
	try {
	  ban();
	} catch (err) { }
})();

无限 debugger 的对策

  • 如果仅仅是加上面那么简单的代码,对于一些技术人员而言作用不大
  • 可以通过控制台中的 Deactivate breakpoints 按钮或者使用快捷键 Ctrl + F8 关闭无限 debugger
  • 这种方式虽然能去掉碍眼的 debugger,但是无法通过左侧的行号添加 breakpoint

禁止断点的对策

  • 如果将 setInterval 中的代码写在一行,就能禁止用户断点,即使添加 logpoint 为 false 也无用
  • 当然即使有些人想到用左下角的格式化代码,将其变成多行也是没用的
(()=> {
  function ban() {
    setInterval(()=> { debugger; }, 50);
  }
  try {
    ban();
  } catch (err) { }
})();


忽略执行的代码

  • 通过添加 add script ignore list 需要忽略执行代码行或文件
  • 也可以达到禁止无限 debugger

忽略执行代码的对策

  • 那如何针对上面操作的恶意用户呢
  • 可以通过将 debugger 改写成 Function("debugger")(); 的形式来应对
  • Function 构造器生成的 debugger 会在每一次执行时开启一个临时 js 文件
  • 当然使用的时候,为了更加的安全,最好使用加密后的脚本
// 加密前
(()=> {
  function ban() {
    setInterval(()=> {
      Function('debugger')();
    }, 50);
  }
  try {
    ban();
  } catch (err) { }
})();

// 加密后
eval(function(c,g,a,b,d,e){d=String;if(!"".replace(/^/,String)){for(;a--;)e[a]=b[a]||a;b=[function(f){return e[f]}];d=function(){return"\w+"};a=1}for(;a--;)b[a]&&(c=c.replace(new RegExp("\b"+d(a)+"\b","g"),b[a]));return c}('(()=>{1 0(){2(()=>{3("4")()},5)}6{0()}7(8){}})();',9,9,"block function setInterval Function debugger 50 try catch err".split(" "),0,{}));


终极增强防调试代码

  • 为了让自己写出来的代码更加的晦涩难懂,需要对上面的代码再优化一下
  • 将 Function('debugger').call() 改成 (function(){return false;})['constructor']('debugger')['call']();
  • 并且添加条件,当窗口外部宽高和内部宽高的差值大于一定的值 ,我把 body 里的内容换成指定内容
  • 当然使用的时候,为了更加的安全,最好加密后再使用
(()=> {
  function block() {
    if (window.outerHeight - window.innerHeight > 200 || window.outerWidth - window.innerWidth > 200) {
      document.body.innerHTML="检测到非法调试,请关闭后刷新重试!";
    }
    setInterval(()=> {
      (function () {
        return false;
      }
      ['constructor']('debugger')
      ['call']());
    }, 50);
  }
  try {
    block();
  } catch (err) { }
})();




禁止的方法有了,如何反禁止呢? 你想到了吗?

文最初发表于HTTP Toolkit网站,经原作者 Tim Perry 授权由 InfoQ 中文站翻译分享。

万物都会有终结,HTTP API 也不例外。不论你的 API 今天看上去多么伟大,迟早有一天你会想发布一个全新的版本,新版本能更好地解决相同问题,在各方面可能都会有所改善,但是它因为有了新参数,与旧版本也无法兼容,或者你只是想彻底关闭旧的 API。总而言之,你现在的 API 不会永远存在。

但是,这并非轻而易举就能完成的,因为你的 API 有客户端。如果你关闭端点、参数或整个 API 而没有做出恰当的警告的话,那他们肯定会非常不爽。

那么,该怎样安全地关闭 API,让你的用户尽可能地感到轻松愉快呢?

在这方面,我们有正确的做事方式,包括两个新的头信息草案,它们正在被新的 IETF “Building Blocks for HTTP APIs”工作组进行标准化,旨在形成一个精确的过程。我们了解一下。

制定计划

初始第一步:检查相关的 API 是否真的有客户端。

希望你能有某些 API 的度量指标,至少在某些地方存有日志。如果没有的话,那把它们添加上,如果你有这些东西的话,并且你能确定没有人再使用这个 API 了,那么恭喜你,你赢了。现在,你就可以把它关掉,删掉代码,不要再管这篇文章了,好好睡一觉。

下一个问题,如果比较遗憾,你无法去睡觉的话,那就要问问自己,除了关闭这个 API,还有没有其他方案。你关闭的所有东西都有可能破坏别人的代码,并且会消耗他们的时间来修复这些问题。如果 API 能继续运行的话,对客户端的生态系统和整个 web 的健康都是有好处的。

在很多场景下,旧的 API 可以在内部进行转换,透明地转化成对新 API 的调用,这样可以避免维护两个完全独立的版本。这是Stripe的API版本管理方式的一个基本组成部分,他们在所有发生变化的 API 中都包含了转换,以确保对不兼容的旧版本 API 的请求能继续像以前那样运行,根据需要自动转换请求和响应从而可以使用较新的代码。

这样的转换并不总是可行的,而且如果永远这样做的话会带来明显的额外复杂性,但是如果你可以做到这一点的话,就能为用户提供非常有价值的稳定性,并且可以节省大量废弃旧版本或维护旧版本相关的工作。

但是,如果这个服务/端点/参数已经用到了生产环境,而且继续支持它是不现实的做法,那么它必须要被淘汰。

要实现这一点,我们就要有一个计划。我们首先要问自己三个关键的问题:

  • 你希望用户该怎么做?常见的答案包括:
  • 升级到相关功能的一个更新的、依然能得到支持的版本
  • 使用一些可替代的端点/参数/服务
  • 使用不同的服务,它们与你无关,不需要你关心
  • 用户应该何时迁离这个 API?你所提出的替代方案现在就可以用了吗?
  • 截止时间是什么时候?也就是,这个 API 何时会完全停止使用?(如果不能完全确定的话,你可以稍微延迟回答这个问题)。

计划准备就绪之后,我们就该把它告诉人们了。

沟通

首先,要把这一决定告诉人们。

发邮件到邮件列表,在 Twitter 或微博上发帖,如果有 API 规范的话,对其进行更新(比如,OpenAPI 在operations和parameters上有一个deprecated字段),并在相关的在线文档上大声强调这一点。

你应该包含上文提到的所有信息:他们应该做些什么作为替代方案,你建议他们什么时候开始迁移以及他们必须要进行迁移的最后期限(如果已经确定期限的话)。

在将这些信息告诉给人们后,接下来就该告诉计算机,而这就是新的 IETF 头信息可以发挥作用的地方。

Deprecation 头信息

Deprecation头信息能告诉客户端请求的资源现在依然像以前那样运行,但是这种方式已经不再推荐使用了。通过一个简单的 HTTP 头信息,我们就可以声明这一点:

Deprecation: true

另外,我们还能提供一个日期。这个日期告诉用户他们何时该开始进行迁移。这个日期可以是一个过去时间(这代表他们应该立即开始迁移),也能是将来时间(通常这意味着他们要迁移到的新环境还没有准备就绪)。如下所示:

Deprecation: Thu, 21 Jan 2021 23:59:59 GMT

如果你要废弃整个端点或服务,那么你可以在每个响应中都带上这样的头信息。如果你想要废弃的是一个具体的特性,可能是一个参数、请求方法或者请求体中的某个特定字段的话,那么你应该在该特性被使用的时候才在响应中包含这个头信息。

为了给客户端更多的信息,我们还可以使用Link HTTP 响应头信息链接至端点或人类易读的文档。在同一个Link头信息中,我们可以包含多个这样的链接,只需要使用逗号进行分割即可(后面我们会看到一个完整的例子)。该规范定义了四个与 API 废弃相关的链接:

Deprecation 链接

我们可以为 deprecation 链接指向一个人类易于阅读的描述:

Link: <https://developer.example.com/deprecation>; rel="deprecation"; type="text/html"

这是告诉用户发生了什么以及他们该怎么办的主要方式。你应该始终使用它。如果还没有完整的详情和最终的关闭日期,那么即使只是一个占位符,这也是很有帮助的。在这种情况下,不要忘记让用户订阅更新,这可以采用邮件列表、RSS 或其他类似的方式来实现。

Latest-Version 链接

如果你希望客户端转移至 API 相同端点的最新版本,那么可以使用该链接指向它,如下所示:

Link: <https://api.example.com/v10/customers>; rel="latest-version"

Successor-Version 链接

如果你的 API 有多个可用的版本,通常最好每次向前迁移一个版本,而不是直接从最老的、现已废弃的版本跳到最新的版本。为了帮助解决这个问题,我们链接至已废弃版本的下一个版本,而不是最新版本,如下所示:

Link: <https://api.example.com/v2/customers>; rel="successor-version"

Alternate 链接

如果该 API 没有新的等价版本,用户最好迁移到一个完全不同的资源,它可能是一个很好的替代方案,那么我们使用 alternate 链接来指明这一点,如下所示:

Link: <https://api.example.com/v2/users/123/clients>; rel="alternate"

Sunset 头信息

如果你知道了 API 何时完全关闭的话,那么就应该添加一个Sunset头信息。

Sunset 头信息告诉客户端 API 何时会停止运行。这是一个强制的截止时间:API 客户端必须要在这个日期前进行迁移,我们承诺在这个时间前不会破坏任何事情。

在这里,我们必须要提供一个时间,它应该是一个未来的时间。不过,如果它是一个过去的时间,这也是可以的:此时就相当于说“这个 API 会在任意时刻关闭,你需要立即停止使用它”。它如下所示:

Sunset: Tue, 20 Jul 2021 23:59:59 GMT

这非常简单,它不仅可以用到 API 关闭的场景中:我们能用它来标记将来 URL 迁移的 HTTP 重定向,或者表明特定 URL 有限的生命周期(适用于临时性的内容,或者适用于具有监管要求的特定资源,比如数据保留策略)。它所说明的就是“这个端点可能在该日期后不会再按照你的预期运行,请做好准备”。

Sunset 链接

该规范也提供了一个 Sunset 链接的关系。按照设计,它会链接至关于关闭特定端点更加详细的信息(如果你有 deprecation 链接的话,它们可能会是同一个)或者关于服务的通用 Sunset 策略。如下所示:

Link: <http://developer.example.com/our-sunset-policy>;rel="sunset";type="text/html"

在这里我们也要指出,通用的 Sunset 策略是非常有用的!Sunset 策略会告诉客户端,当我们关闭端点的时候(比如,一年后替代方案上线),用户该如何确保他们能得知这一情况(邮件列表、状态页面、HTTP 头信息等)以及他们通常应该做些什么(更新、检查文档、遵从Link头信息)。

如果马上就要废弃某个 API 的话,添加这样的链接作用其实不大,但是如果你能在一年前就将其发布出去的话,你的客户端可能已经为此做好了准备。

除此之外,发布 Sunset/Deprecation 策略的最好时间就是现在。如果你恰好正以某种方式编写 Deprecation 文档的话,这么做是值得考虑的。

组合到一起

按照设计,这些组成部分能很好地协作。例如,为了表明某个最近废弃的 API,该 API 会在 6 个月内彻底关闭,我们要链接至文档并提供下一个版本的直接链接,那么我们应该在响应中包含如下的头信息链接:

Deprecation: Thu, 21 Jan 2021 23:59:59 GMT
Sunset: Tue, 20 Jul 2021 23:59:59 GMT
Link: <https://api.example.com/v2/customers>; rel="successor-version",
    <https://developer.example.com/shutting-down-customers-v1>; rel="deprecation"

渐进式关闭

如果所有这些都已经准备到位,并且 sunset 截止时间已过,那么我们就可以将 API 关闭了。

但是,这并不意味着你需要立即且彻底消灭该 API。渐进式关闭能有助于确保任何使用该 API 的所有客户端都有最后的机会在它彻底消失前得到最后一次警告。GitHub 在 2018 年移除一些加密支持的时候曾经这样做:首先禁用一个小时,然后启用它,最后在两周后彻底禁用了它。

这里还有另外一个技巧:安卓在 2015 年为已废弃的原生API增加了越来越多的延迟,在彻底关闭 API 前,最终达到了 16 秒的等待。

这些渐进式的关闭为那些错过截止日期的客户端提供了一些灵活性,并且能帮助那些没有注意到废弃时间点的客户端,从而能在 API 彻底关闭之前处理一些问题。

谨慎行事

不管采用哪种方式,只要你尽了最大的努力去沟通关于 API 关闭的事情,那么现在就可以关闭端点/特性/整个服务,删除代码,然后睡个好觉。

像这样小心谨慎地进行废弃和关闭,可以让你的客户端尽可能清楚地知道他们该如何依赖你的 API,何时需要采取行动,以及他们需要做什么。这种变更可能是一件大事儿,这些信息是很重要的。

这些新的草案头信息让我们不仅可以与人类沟通,还能将这些信息暴露给自动化系统。随着这些头信息的普及,我很高兴地开始看到有更多的工具建立在它们之上。通用的 HTTP 客户端可以根据这些数据自动记录有用的警告日志,API 生成器本身也能根据 API 规范处理越来越多的问题,而 HTTP 调试器(如HTTP Toolkit)可以在截获的实时流量中为你突出显示废弃端点的使用。这是一个令人激动的时刻,我们可以开始安全地关闭 API 了!

需要注意的是,这些头信息是 HTTP 规范的草案。在最终确定前,它们有可能会发生变化。也就是说,它们经历了几轮修改,从现在开始,它们不太可能发生巨大的变化,现在能广泛测试它们了。

不过这也意味着还有时间进行反馈! 如果你对它们的工作方式和如何更好地运行有想法的话,请与“Building Blocks for HTTP APIs”工作组联系。你可以给邮件列表发邮件:httpapi@ietf.org,或者在这里查看之前的邮件列表讨论。

原文链接:

https://httptoolkit.tech/blog/how-to-turn-off-your-old-apis/

这两天碰到一个需求:在用户刷新页面或者关闭页面的时候,前端要给后台发一条请求,释放该页面的授权占用。

分析了一下,这不就是在页面卸载时发请求嘛,三下五除二就实现一版:

window.addEventListener("beforeunload", ()=> {
let oReq=new XMLHttpRequest();
oReq.open("POST", "http://127.0.0.1:1991/loginout");
oReq.send(JSON.stringify({name: "编程三昧"}));

测试发现:

  • 刷新页面时基本满足需求(偶尔也会有后台接收不到请求的现象,但概率很低)
  • 关闭页面时,后台接收不到请求

既然异步 Ajax 不行,那就试试同步的吧,结果直接报错了:

搜了一下,解释如下:

Chrome now disallows synchronous XHR during page dismissal when the page is being navigated away from or closed by the user. This involves the following events (when fired on the path of page dismissal): beforeunload, unload, pagehide, and visibilitychange.

概括起来就是:对现在的 Chrome 来说,在页面导航离开或者被用户关闭时,不允许发送同步 XHR 请求,涉及到的事件有:beforeunload、unload、pagehide 和 visibilitychange。

虽然问题没解决,但是却长知识了,这波不太亏!

navigator.sendBeacon()

后来通过搜索,看到有一个接口是专门来干这事的,那就是 navigator.sendBeacon()

描述

这个方法主要用于满足统计和诊断代码的需要,这些代码通常尝试在卸载(unload)文档之前向web服务器发送数据。

语法

navigator.sendBeacon(url, data);

参数

  • url 表明 data 将要被发送到的网络地址。
  • data 参数是将要发送的 ArrayBufferViewBlobDOMString 或者 FormData 类型的数据。

返回值

当用户代理成功把数据加入传输队列时,sendBeacon() 方法将会返回 true,否则返回 false

实现

既然有了接口,那实现起来就简单了。

代码

 window.addEventListener("beforeunload", (e)=> {
    const data={name: "编程三昧"};
    window.navigator.sendBeacon("http://127.0.0.1:1991/loginout", JSON.stringify(data));
});

效果

不管是刷新页面还是关闭页面,后台都能接收到前端发送过来的请求,完美实现需求。

总结

浏览器现在功能越来越强大,支持的 API 也越来越丰富,放在之前很难实现的功能,现在可能就是轻而易举的事,还是要多关注技术动态。

~

~本文完,感谢阅读!

~

学习有趣的知识,结识有趣的朋友,塑造有趣的灵魂!

大家好,我是〖编程三昧〗的作者 隐逸王,我的公众号是『编程三昧』,欢迎关注,希望大家多多指教!

你来,怀揣期望,我有墨香相迎! 你归,无论得失,唯以余韵相赠!

知识与技能并重,内力和外功兼修,理论和实践两手都要抓、两手都要硬!