前言
在做项目的过程中,遇到的一个业务场景。
前端使用微信小程序进行课堂扫码签到,后端接收签到请求,并且需要对于一些可能的作弊手段予以反制,比如一个人登录多个人账号帮他们签到,或者一部手机切换不同微信进行签到。
问题分析
想要限制签到作弊,需要的就是在用户签到时,对于一次签到,一个人,一个设备,进行唯一性标识,对于这部分重复的进行限制。
而如果需要唯一性标识,对于web页面来说也有cookie来实现,但是微信小程序很难找到一个可以标识设备唯一性的方案,微信官方也没有提供相关接口。
解决方案
阅读了微信小程序的相关开发文档之后,想出了一套解决方案。
首先,一个微信账号对于小程序会有一个唯一的OpenID
。
微信小程序登录流程
- 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
- 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key。
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
那么对于一次签到来说,限制OpenID
的重复即可限制用户使用一个微信切换绑定不同账号完成签到,只需要给数据库增加一个字段,记录该次签到的OpenID
即可。
切换绑定账号的问题就解决了,那么对于同一设备切换微信账号来说,微信小程序官方提供了一个接口可以获取设备的系统相关信息。
微信获取系统信息接口文档
wx.getSystemInfo(Object object)
功能描述:获取系统信息。
能够获取设备型号,设备信息,字体大小,系统设置等等相关的一系列信息。
而这些设备信息可以拼接起来并md5加密生成一个设备指纹来作为设备唯一标识,这样就可以得到一串长度固定的设备标识码deviceInfo
。
再加上获取签到请求的ip地址和deviceInfo
拼接,就能得到小范围内基本不会出现重复的设备指纹deviceCode
。
PS:对于ip地址的获取,需要对真实ip进行过滤,否则获取到的可能是代理服务器的链路ip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
private static final String[] PROXYS = {"x-forwarded-for", "Proxy-Client-IP", "WL-Proxy-Client-IP", "X-Real-IP", "HTTP_CLIENT_IP"};
public static String getIpAddr(HttpServletRequest request) { String ipAddress = null;
try { for (String proxy : PROXYS) { ipAddress = request.getHeader(proxy); if (StringUtils.isNotBlank(ipAddress) && !"unknown".equalsIgnoreCase(ipAddress)) { return ipAddress; } } if (StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals("127.0.0.1")) { InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress = inet.getHostAddress(); } } if (ipAddress != null && ipAddress.length() > 15) { if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress = ""; } return ipAddress; }
|
最终结论
微信小程序扫码签到业务,用OpenId
来防止微信绑定不同账号,用设备指纹deviceCode
来防止同一台设备一定范围内切换不同微信签到
deviceCode=md5(请求ip+deviceInfo)
deviceInfo=md5(若干设备信息字段)
生成设备信息deviceInfo
所取的字段值越多越能准确标记一部设备的唯一性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
wx.getSystemInfo({ success(res) { delete(res.deviceOrientation) delete(res.enableDebug) delete(res.wifiEnabled) delete(res.bluetoothEnabled) delete(res.locationEnabled) app.globalData.deviceInfo = hexMD5(JSON.stringify(res)) } })
|
1 2 3 4 5 6 7 8 9 10
| public Result signIn(String openId, int signInId, HttpServletRequest request, String deviceInfo) { String deviceCode = md5(IpUtil.getIpAddr(request)+deviceInfo); }
|
方案缺点
- 这不能作为长时间段下用户设备唯一性的标识,因为比如换个网络环境,ip地址就可能发生变化,只能作为小范围内标识唯一设备的方案,比如本文描述的课堂扫码签到场景
- 如果极端场景下,比如完全一模一样的两部手机,从型号品牌到各种设置都完全一致,并且处于机房局域网之类的导致请求ip相同的场景,会导致该方案出问题,没法标识唯一设备