投票类应用防刷总结


(图片来之维基百科:日本投票箱

这里说的投票是网络投票,有句话说网络投票比的就是刷票,可见防刷票一直都是一个难题。虽然不能完全防止,但是我们是可以增加一些逻辑限制。

接触到一些投票性质的场景,比如微博投票投票产品、知乎投票、投票活动、抽奖活动、加油、应援、明星打榜等。

以下总结了用到或接触到的防刷思路

一、参数检查
0、参数格式检查: 参数数量、类型的检查
1、参数有效性检查:检查是否有意义,比如活动id是否存在,选手id是否存在(防止非法请求投票接口,写入脏数据)

二、Referer检查

三、登陆
登陆用户才能投票

四、每个用户投票数限制
0、每个投票对象进行投票数限制
例如每个选手每天投票上限10票

1、半小时同一uid投票数阈值150
算法和实现同半小时IP数限制,把IP改为uid即可

五、用户等级限制(如果有,防止垃圾账号刷票)
获取投票用户等级,过滤低等级用户。

六、IP限制
常用方案:
0、针对一场投票活动,半小时同一IP投票数阈值150
算法:
0.0 用Redis或MC来存储数据
0.1 用当前请求IP和固定前缀作为cache的key, IP数作为值。
0.2 每次先根据请求IP获取投票数,如果超过阈值,返回异常;否则投票数加一,并更新缓存数据。
0.3 实现伪码

class Poll
{
    public function canPoll($ip = null)
    {
        // 1800秒 阈值150
        $limit_ip_num = 150;

        $mc = Comm_Mc::init();
        $cache_conf_ip = 'poll_ip_total_%s';

        // 检查ip限制
        if (empty($ip)) $ip = Comm_Context::get_client_ip();

        $ip_num = $mc->getData($cache_conf_ip, array($ip));
        if ($ip_num && $ip_num >= $limit_ip_num) {
            return '操作过于频繁,请稍候再试';
        }

        if ($ip_num === false) {
            $ip_num = 1;
        } else {
            $ip_num++;
        }

        $rs = $mc->setData($cache_conf_ip, array($ip), $ip_num);

        return '成功';
    }
}

1、5分钟内, 同一投票对象,同一IP,对应用户数大于300。

/**
     * 功能: ip限制【规则一: 同一个oid, 同一个IP, 对应用户数大于某个阈值】
     * 
     * @exp 5分钟内,同一个oid, 同一个IP, 对应用户数 >= 300
     * 
     * @param $aid int 活动ID
     * @param $oid int 投票项ID
     * @param $uid int 用户ID
     * @param $ip  string IP地址
     * @param $duration  int 时长(分钟)
     * @param $threshold int 阈值
     * 
     * @return boolean
     */
    protected static function ip_limit_rule1($aid, $oid, $uid, $ip, $duration, $threshold) {
        $redis_key = sprintf(Tool_Conf::get('redis_key.ssvote_security_iplimit_rule1'), $aid, $oid, $ip);
        $redis = Comm_Redis::init(self::REDIS_POOL, true);
        $count = $redis->sCard($redis_key);

        if ($count == 1) $redis->expire($redis_key, $duration * 60);

        if ($count >= $threshold) return false;

        $redis->sAdd($redis_key, $uid);

        return true;
    }

2、同一投票对象,IP前两段相同对应用户数大于500。

/**
     * 功能: ip限制【规则一: 同一个oid, 同一个IP前两段相同, 对应用户数大于某个阈值】
     * 
     * @exp 5分钟内,同一个oid, IP前两段相同对应用户数 >= 500
     * 
     * @param $aid int 活动ID
     * @param $oid int 投票项ID
     * @param $uid int 用户ID
     * @param $ip  string IP地址
     * @param $duration  int 时长(分钟)
     * @param $threshold int 阈值
     * 
     * @return boolean
     */
    protected static function ip_limit_rule2($aid, $oid, $uid, $ip, $duration, $threshold) {
        list($segment1, $segment2, , ) = explode('.', $ip);

        $segment = $segment1 . '.' . $segment2;
        $redis_key = sprintf(Tool_Conf::get('redis_key.ssvote_security_iplimit_rule2'), $aid, $oid, $segment);
        $redis = Comm_Redis::init(self::REDIS_POOL, true);

        $count = $redis->sCard($redis_key);
        if ($count == 1) $redis->expire($redis_key, $duration * 60);

        if ($count >= $threshold) return false;
        $redis->sAdd($redis_key, $uid);

        return true;
    }

3、同一用户uid, 5分钟对应IP数大于4

/**
     * 功能: ip限制【规则一: 同一个uid 指定时间内,对应IP数大于某个阈值】
     * 
     * @exp 同一个uid 5分钟内,对应IP数 >= 500
     * 
     * @param $aid int 活动ID
     * @param $uid int 用户ID
     * @param $ip  string IP地址
     * @param $duration  int 时长(分钟)
     * @param $threshold int 阈值
     * 
     * @return boolean
     */
    protected static function ip_limit_rule3($aid, $uid, $ip, $duration, $threshold) {
        $redis_key = sprintf(Tool_Conf::get('redis_key.ssvote_security_iplimit_rule3'), $aid, $uid);
        $redis = Comm_Redis::init(self::REDIS_POOL, true);
        
        $count = $redis->sCard($redis_key);
        if ($count == 1) $redis->expire($redis_key, $duration * 60);

        if ($count >= $threshold) return false;

        $redis->sAdd($redis_key, $ip);
        
        return true;
    }

4、同一IP投票次数大于100

 /**
     * 功能: ip限制【规则一: 指定时间内,同一个IP投票次数大于某个阈值】
     * 
     * @exp 30分钟内,同一个IP投票次数 >= 100
     * 
     * @param $aid int 活动ID
     * @param $ip  string IP地址
     * @param $duration  int 时长(分钟)
     * @param $threshold int 阈值
     * 
     * @return boolean
     */
    protected static function ip_limit_rule4($aid, $ip, $duration, $threshold) {
        $redis_key = sprintf(Tool_Conf::get('redis_key.ssvote_security_iplimit_rule4'), $aid, $ip);
        $redis = Comm_Redis::init(self::REDIS_POOL, true);

        $count = $redis->incrBy($redis_key, 1);
        if ($count == 1) $redis->expire($redis_key, $duration * 60);

        if ($count > $threshold) return false;

        return true;
    }

注:以上时间和阈值可调可配。

七、用户行为验证
0、检查投票前5分钟有没有指定用户曝光日志。

1、请求投票接口携带指定token
可以增加刷票成本,如果是从页面发起的投票行为默认带上token, 直接请求接口不会有。

2、两次投票时间间隔小于2秒。
意义不大。如果可以投票多票,一般可以一次投满。

八、设备验证
同一设备下,一个用户uid数超过3个。

注:不太准,有些设备唯一标示可能获取不到。

九、滑动弹层验证
0、一般对接公司风控体系,24小时出一次弹层。
例如之前公司风控体系对接极验的弹层服务

大概流程:
申请接入风控服务,申请entry参数
根据entry调通行证接口获取风控ID:flow_id
redirect到风控服务接口,带上flow_id,当前页面地址作为callback地址
风控服务出验证弹层
验证通过后回到callback地址
当前页面再次投票的时候调风控验证服务验证是否认证通过,认证信息10分钟有效。

具体表现如下图

1、图片验证码或短信验证码,个人觉得成本略高(手机短信收费成本、用户验证繁琐成本)

参考:
https://segmentfault.com/a/1190000000656336

发表评论

电子邮件地址不会被公开。 必填项已用*标注