如何防止薅羊毛,一直是App头疼且需要注意的问题,前端能做到真正的防黑产吗?答案是否定的,也就是说,通过前台,我们是无法知道当前用户是否是真实用户的,毕竟Android是不安全的,可以模拟用户的一系列操作手势,也可以通过抓取相应的接口,模拟相应的请求,一个黑产想要达到他想要的目的,无论程序再安全,也无非是破解难度的问题。
apk的混淆加固,相关密钥的安全存储,以及接口的加密,都是我们加深App安全的操作,也是进一步加深黑产用户的破解难度,对于数据的流失,防黑产,一般都是和接口挂钩,大部分也都是服务端需要考虑的,毕竟交互的最终结果都是传到服务端,无非前端做一些配合服务端的事情。
那么除了以上的相关处理,前端是否还有其他的简单逻辑,告知服务端,当前用户是否是真实用户呢?当然是可以的,说好了,只能是简单,纯粹的简单,不参杂一丁点的复杂思想。实现思路,其实就是针对项目中的用户最常用行为轨迹,进行埋点统计,如果触发了这个行为轨迹,就告知服务端,这是一个真实用户;这种方式只能简单的进行统计,主观意识上认为它是真实用户,并不能做到百分百,但也能起到一定的作用,比如可以定位那些暴力的刷接口行为,通过这种方式,我们就可以避免这种行为。
一个项目中的功能有很多,比如电商中的购物,及时通讯中的聊天等等,针对不同的App,肯定有用户最常用的功能,这就是大概率的用户习惯,针对这些用户习惯,我们可以设计相对应的路径,也就是用户,通过哪些事件后,进入到了最后的功能,比如购物,用户触发了商品列表,点击了商品后进入到商品详情,然后点击购买,最后发起了支付,这就是一个路径,我们可以称为是一个事件链。
事件的统计方式:
一个事件链,从开始到结束,可能有很多事件,但每一个事件链是唯一的,如下图的事件链ABC,记录好定义的事件链,就可以针对当前事件链,进行逐个事件统计,如下图中的123,到end结束,end为当前事件链结束的标识,得到结束的标识后,就得到了一个用户的执行流程,就可以上报到服务端。
举例(登录操作),定义事件链为login:
打开应用-->点击我的-->立即加入-->输入手机号-->输入验证码-->点击登录-->登录成功
以上是登录从开始到结束的整个事件链流程,针对每一个事件进行存储,除最后一个事件是end标识,前边的都为存储类型,当执行到end类型后就可以上报此事件链。
事件链的上传结果,包含用户的id,各个事件的触发标识。
具体实现:
基本的逻辑就是如上,代码也不复杂,总共可以分为三个方面,第一个是事件的统计,其实和埋点的思路一致,第二个是,针对事件链或者事件的存储,第三个就是事件的上传。
class BlackProductionUtils {
private val mPreferences = "gwmBlack"
private val mSpEvent = "spEvent"
private var mSp: SharedPreferences? = null
private var mOnce: Boolean = false//全局单次上传,所有事件链只上报一次
companion object {
@JvmStatic
private var instance: BlackProductionUtils? = null
get() {
if (field == null) {
field = BlackProductionUtils()
}
return field
}
@JvmStatic
fun get(): BlackProductionUtils {
return instance!!
}
}
/**
* AUTHOR:AbnerMing
* INTRODUCE:once:是否一个事件链只执行一次
*/
fun init(context: Context, once: Boolean = false) {
mOnce = once
mSp = context.getSharedPreferences(mPreferences, 0)
}
/**
* AUTHOR:AbnerMing
* INTRODUCE:添加事件,key为事件链标识,position为事件索引,
* start是事件开始,end是结束事件的标识
*/
fun addEvent(
key: String, position: Int,
start: Boolean = false,
end: Boolean = false
) {
try {
//首先判断是否单次上报,如果是,取出标识,存在就中断执行
if (mOnce && getString(mSpEvent).contains(key)) {
return
}
if (start) {
//进行清空事件,从新开始
put(key, "")
}
//首先获取
var eventData = getString(key)
if (!TextUtils.isEmpty(eventData)) {
//证明存在数据
eventData += containsPosition(eventData, position)
} else {
//证明不存在
eventData = position.toString()
}
if (end) {
//结束,进行上报此事件
uploadEvent(key, eventData)
} else {
//无结束
put(key, eventData)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* AUTHOR:AbnerMing
* INTRODUCE:是否包含
*/
private fun containsPosition(eventData: String, position: Int): String {
val p = position.toString()
return if (!eventData.contains(p)) {
p
} else {
""
}
}
/**
* AUTHOR:AbnerMing
* INTRODUCE:上报事件,key:事件链标识,eventData:一个事件链的所有事件
*/
private fun uploadEvent(key: String, eventData: String) {
val spEvent = getString(mSpEvent)
if (mOnce) {
if (!spEvent.contains(key)) {
addSpKey(key)
//单次进行上传
reportEvent(key, eventData)
}
} else {
//每次进行上传
reportEvent(key, eventData)
}
}
/**
* AUTHOR:AbnerMing
* INTRODUCE:走接口,统一上报
*/
private fun reportEvent(key: String, eventData: String) {
}
/**
* AUTHOR:AbnerMing
* INTRODUCE:保存事件链Key
*/
private fun addSpKey(key: String) {
//记录每一个事件链标识
var spKey = getString(mSpEvent)
spKey += key
put(mSpEvent, spKey)
}
private fun put(key: String, value: String) {
try {
mSp!!.edit().putString(key, value).commit()
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getString(key: String, defaultVal: String = ""): String {
var mValue = ""
mSp?.let {
mValue = it.getString(key, defaultVal)!!
}
return mValue
}
/**
* AUTHOR:AbnerMing
* INTRODUCE:清除所有的事件
*/
fun clearEvent() {
mSp?.let {
it.edit().clear().commit()
}
}
}
调用
1、初始化
默认每次事件链执行完都上传
BlackProductionUtils.get().init(this)
一个事件链只上传一次
BlackProductionUtils.get().init(this,true)
2、添加事件
默认false,不清除原有事件,true为每次事件开始时,都会重新记录事件,具体情况,比如当前事件链有10个事件,用户触发了前5个,后面不走了,隔了一段时间又从事件1开始了,之前存储的事件,清除还是保存,就是这个意思。
事件开始
BlackProductionUtils.get().addEvent("main", 0, true)
正常的事件存储,实际会有多个
BlackProductionUtils.get().addEvent("main", 1)
事件结束
BlackProductionUtils.get().addEvent("main", 3, end = true)
告知服务端调用reportEvent方法即可。
相关的逻辑就是如上代码,可以根据自己项目的存储方式,或者其他逻辑进行更改。大家可以简单的认为就是埋点,然后进行上报服务端,服务端会根据每个用户上报的事件链,来进行统计分析。
防黑产,以上的方式,开头已经陈述,并不能做到百分百,通过多个事件链,但也能够有效的过滤一些非真实用户,当然了,具体情况具体分析,你说它有意义,它就存在意义,若没意义,其实也没任何意义。