背景介绍
微信投票在这几年一直很热门,只要是个活动往往都做一个投票的功能。刷票已形成了一个庞大的产业链但如何防止刷票行为就很让人头疼了。首先要清楚微信的刷票行为,微信投票是根据openid来判断一个用户是否已投过票。
openid是加密后的微信号,每个用户对每个公众号的openid是唯一的。
这个判断依据有较大的漏洞,就是只能判断openid是否重复,但无法校验openid是不是真实的。而且就算openid是真实的,刷票软件也有批量的正确openid。刷票软件就是通过使用HttpClient等类似客户端发包,把openid和投票信息post至服务器。由于刷票软件动态伪装ip,拥有大量openid,很容易就在没有完善防刷的应用投大量的票。
几种防止刷票的方法
1、只有关注了公众号才能投票
在服务调用获取用户基础信息的API
https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
access_token是全局调用凭证
接口会返回以下数据
{
"subscribe": 1, // 1为关注 0为未关注
"openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M",
"nickname": "Band",
"sex": 1,
"language": "zh_CN",
"city": "广州",
"province": "广东",
"country": "中国",
"headimgurl": ,
"subscribe_time": 1382694957,
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
"remark": "",
"groupid": 0
}
当用户投票且未关注时,我们可根据subscribe是0还是1判断有没有关注,若没有关注则转至公众号的二维码页面并提示用户先长按二维码关注公众号。此方法的缺点就是每次投票都要与微信服务器进行交互。
2、判断refer和User-Agent
以下为一个request header的部分参数示例:
Host:
localhost:8080
Origin:
http://localhost:8080
Pragma:
no-cache
Referer:
http://www.example.com/vote.jsp
User-Agent:
Mozilla/5.0 (iPhone; CPU iPhone OS 9_0_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko)
Mobile/13A452 MicroMessenger/6.2.3 NetType/WIFI Language/zh_CN
X-Requested-With:
XMLHttpRequest
Referer为上一个访问的页面,所以refer必须要为投票的页面地址。
User-Agent里面必须有关键词MicroMessenger
3、限制客户端投票次数
用ip当成同一个ip投票次数受限制,由于很多时候使用nginx或apache之类的代理服务器,因此直接使用HttpServletRequest的getRemoteAddr()很多时候取得的是代理服务器的ip,而我们要取得的是真实的ip址。
下面是一个获取真实ip的示例代码
public String getIpAddr() {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("CLIENTIP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
很多刷票软件都使用ip代理池,所以ip限制只能一部分刷票行为。
4、当投票量大于阅读量时就是刷票行为
存储页面阅读量,投票完成后,计算票数若投票量大于阅读量时就是刷票行为。
5、在提交表单数据的地方使用校验码
服务端返回一个校验码,在dom初始化的时候页面使用js加密这个校验码,commit的时候提交这个加密的校验码,服务端再判断这个加密码的校验码是否正确。校验码设置使用N次后就作废。
以下为伪代码:
- jsp页面
<%=request.setAttribute("_check_code_", UUID.randomUUID())%>;
var _check_code_salt_ = "gx=**&^%%$$###@#$---eeax221";
$(function(){
var _check_code = '<%=request.getAttribute("_check_code_")%>';
});
$.ajax({
url: "http://www.example.com/vote.do",
params: { checkCode: md5(_check_code + _check_code_salt_ ), openid: OPENID },
type:"post",
success:function() {
}
});
- 服务端处理
String checkCodeSalt = "gx=**&^%%$$###@#$---eeax221";
String serverCode = MD5.get((String) request.getAttribute("_check_code_") + checkCodeSalt);
String pageCode = (String) request.getAttribute("checkCode");
if (serverCode.equals(pageCode)) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = null;
out = response.getWriter();
out.append("{errorCode:'1'}");
return;
} else {
doSomething();
}
注:加一个加密的过程是为了让使用HttpClient的模拟访问行为变的更困难
以上的方法可结合项目的实际情况组合使用,五项组合起来使用基本上能杜绝大部分的刷票行为。