家贼难防
现场反馈一些业务上不可以取消的订单被莫名奇妙的取消了。
代码本身没有任何问题,要知道我们对界面上对于订单是否可以cancel的控制如下:
// 调用服务查询订单是否可以cancel
Action.checkCanCancelOrder({
orderNbr:orderNbr}, function(canCancel) {
// 可以cancel,则cancel按钮设置disabled为false,可以点击
if (canCancel) {
this.$(".js-cancel-order").attr("disabled", false);
}
// 否则设置disabled为true,不可点击
else {
this.$(".js-cancel-order").attr("disabled", false);
}
})
客户调查跟踪发现是:
客户的营业员自己通过浏览器开发者工具,修改页面按钮disabled,将不可操作的cancel按钮修改成了可操作的。
即将cancel按钮对应的dom元素的disabled属性删除,这样cancel按钮就可以点击了,订单也就被取消了。
咱就是说,这家贼难防啊!!!
于是乎,客户就要求我们能够对cancel功能进行限制,限制住这种非法行为,并且需要记录非法操作人员的信息,以期望能够遏制惩罚这种行为。
防贼大法百家争鸣
如何避免这类操作呢?群里的大佬们给出了自己的解决方案
大佬A:抽象管理,基于服务,统一校验。。。
大佬A发言如下:
这是普通黑客的一般操作~
他今天试这个 明天就可以试别的~
总有一天,能找到一个能获利的接口。
等他找到那个接口,那就是安全一级故障了~[流汗]
不过放心 我们有方案~等会开完会一起当面讨论下
听到大佬发言,心里一阵狂喜,原来产品已经实现某种限制了吗,是否是能拿来即用?
最后在期待中,大佬来了,洋洋洒洒三千言,什么抽象管理,什么相对统一校验,什么基于服务,支持配置。。。
听得我晕晕乎乎,最后才听明白——只是一个设想,目前还没有做出来。。。嗯最关键的是,就算有,那根本的业务逻辑还是得定制自己处理。。。
暂时不能付诸行动,先放一放吧。。。
大佬B:禁用开发者工具
既然是从浏览器开发者工具去修改的,那就从这个源头解决。比如对F12加个监听,禁止打开开发者工具弹窗
这个网上有一些实践了,比如代码如下:
/*禁用f12建*/
window.onkeydown = window.onkeyup = window.onkeypress = function (event) {
// 判断是否按下F12,F12键码为123
if (event.keyCode === 123) {
event.preventDefault(); // 阻止默认事件行为
window.event.returnValue = false;
}
}
或者其他一些实现方式。
虽然说也是一种解决办法,但是不能从根本上解决问题——开发者工具也可以通过其他方式打开——而且总不能因为这一个改动增加我们自己开发debug的障碍。
大佬C:双重校验
双重校验。即服务端cancel前再对订单进行一次能否cancel的校验。
点击cancel后 真正cancel前再进行一次能否cancel的校验。代码如下:
// 原逻辑
public int cancelOrder(request) {
doCancel(request);
}
// 修改为
public int cancelOrder(request) {
// 先校验是否可以cancel
boolean cancelCancel = checkCancel(request);
if (cancelCancel) {
doCancel(request);
}
else {
// 记录操作员信息
doLog(request);
}
}
大佬D:更加巧妙的双重校验
双重校验的确能快速的解决问题,但是增加了校验逻辑。目前来看我们查询能否cancel的逻辑比较复杂,这样可能有性能的影响。或许我们可以换种方法来进行双重校验。
当第一次校验能否cancel后,如果可以cancel,服务端可以返回给前台一把钥匙cancelKey,cancelKey是由订单某些信息加密而成,无法破解。当点击cancel后需要带着这把钥匙,提交给服务端,服务端根据订单信息重新计算加密的key来验证是否正确,如果正确则继续取消订单。否则就是非法操作,将信息记录下来。
代码如下:
- 校验订单能否取消的服务 增加cancelKey的返回
public response checkCanCancel(request) {
boolean canCancel = doCheck(request);
if (canCancel) {
response.setCancel(true);
// 根据订单信息计算出一个cancelKey 加密
String cancelKey = calCancelKey(request);
response.setCancelKey(cancelKey);
}
return response;
}
- 前台根据返回结果控制cancel按钮disabled,并记录cancelKey
// 调用服务查询订单是否可以cancel
Action.checkCanCancelOrder({
orderNbr:orderNbr}, function(response) {
// 可以cancel,则cancel按钮设置disabled为false,可以点击
if (response.canCancel) {
this.$(".js-cancel-order").attr("disabled", false);
this.cancelKey = response.cancelKey;
}
// 否则设置disabled为true,不可点击
else {
this.$(".js-cancel-order").attr("disabled", false);
this.cancelKey = null;
}
})
- 再次点击cancel时前台需要传参cancelKey
// 调用cancelOrder服务
Action.cancelOrder({
orderNbr:orderNbr, cancelKey:this.cancelKey}, function(response) {
//....
})
- 后台cancelOrder服务校验cancelKey是否正确
public int cancelOrder(request) {
// 计算cancelKey
String newCancelKey = calCancelKey(request);
// 判断cancelKey是否正确
if (newCancelKey.equals(request.cncelKey)) {
doCancel(request);
}
else {
// 记录操作员信息
doLog(request);
}
}
打贼后记
最终我们为了快速解决问题,采用了大佬D的解决办法。
升级后一定程度上遏制了家贼的猖狂行为,并为惩处提供了一定的证据。
不知道大家有没有遇到了类似的奇葩问题?有没有大佬再指点一二,给出更合适的解决办法?
欢迎在评论区留言!!!