public VCodeCheckResponseModel VCodeCheck(VerifyInfoModel verifyInfo, string userIp) { VCodeCheckResponseModel rtnResult = new VCodeCheckResponseModel(); // 允许的偏移量(点触容错) int allowOffset = 10; #region 尝试从内存中取出对应的 VCodeKey // 获取此用户会话的验证码效验 vCodeKey string cacheKeyVCodeKey = CachePrefixVCodeKey + verifyInfo.UserId; if (!_cacheHelper.Exists(cacheKeyVCodeKey)) { // 验证码无效,1.此验证码已被销毁 rtnResult = new VCodeCheckResponseModel { code = -5, message = "验证码过期, 获取新验证码" }; return(rtnResult); } string rightVCodeKey = _cacheHelper.Get <string>(cacheKeyVCodeKey); // AES解密 string vCodeKeyJsonStr = AesHelper.DecryptEcbMode(rightVCodeKey, _options.AesKey); // json -> 对象 VCodeKeyModel vCodeKeyModel = null; try { // TODO: fixed: 临时修复, 直接将全部为0的字节去除, byte[] bytes = Encoding.UTF8.GetBytes(vCodeKeyJsonStr); byte[] remove0Bytes = bytes.Where(m => m != 0).ToArray(); string remove0ByteStr = Encoding.UTF8.GetString(remove0Bytes); // 能够转换为 对象, 则说明 vCodeKey 无误, 可以使用 //vCodeKeyModel = JsonHelper.Deserialize<VCodeKeyModel>(vCodeKeyJsonStr); vCodeKeyModel = JsonHelper.Deserialize <VCodeKeyModel>(remove0ByteStr); } catch (Exception ex) { // TODO: BUG: 经加密再解密后的jsonStr,虽然看起来一样,但发生了一点改变, 导致无法转换 // '0x00' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 110. } if (vCodeKeyModel == null) { // 验证码无效,被篡改导致解密失败 rtnResult.code = -3; rtnResult.message = "验证码无效, 获取新验证码"; return(rtnResult); } #endregion #region 验证码是否过期 // 验证码是否过期 bool isExpired = ((DateTimeHelper.NowTimeStamp13() - vCodeKeyModel.TS) / 1000) > _options.ExpiredSec; if (isExpired) { // 验证码过期 rtnResult.code = -4; rtnResult.message = "验证码过期, 获取新验证码"; RemoveCacheVCodeKey(verifyInfo.UserId); return(rtnResult); } #endregion #region 效验点触位置数据 // 效验点触位置数据 IList <PointPosModel> rightVCodePos = vCodeKeyModel.VCodePos; IList <PointPosModel> userVCodePos = verifyInfo.VCodePos; // 验证码是否正确 bool isPass = false; if (userVCodePos.Count != rightVCodePos.Count) { // 验证不通过 isPass = false; } else { isPass = true; for (int i = 0; i < userVCodePos.Count; i++) { int xOffset = userVCodePos[i].X - rightVCodePos[i].X; int yOffset = userVCodePos[i].Y - rightVCodePos[i].Y; // x轴偏移量 xOffset = Math.Abs(xOffset); // y轴偏移量 yOffset = Math.Abs(yOffset); // 只要有一个点的任意一个轴偏移量大于allowOffset,则验证不通过 if (xOffset > allowOffset || yOffset > allowOffset) { isPass = false; } } } #endregion #region 未通过->错误次数达到上限? if (!isPass) { // 本次没通过验证 -> 错误次数+1 vCodeKeyModel.ErrorNum++; // 错误次数是否达上限 bool isMoreThanErrorNum = vCodeKeyModel.ErrorNum > _options.AllowErrorNum; if (isMoreThanErrorNum) { // 错误 -> 2.code:-2 验证码错误 且 错误次数已达上限 -> message: 这题有点难,为你换一个试试吧 rtnResult.code = -2; rtnResult.message = "这题有点难, 为你换一个试试吧"; RemoveCacheVCodeKey(verifyInfo.UserId); return(rtnResult); } else { // 错误 -> 1.code:-1 验证码错误 且 错误次数未达上限 -> message: 点错啦,请重试 string vCodekeyJsonStrTemp = JsonHelper.Serialize(vCodeKeyModel); // AES加密 vCodekeyJsonStrTemp string vCodeKeyStrTemp = AesHelper.EncryptEcbMode(vCodekeyJsonStrTemp, _options.AesKey); // 更新 Cache 中的 vCodeKey _cacheHelper.Insert <string>(CachePrefixVCodeKey + verifyInfo.UserId, vCodeKeyStrTemp); rtnResult.code = -1; rtnResult.message = "点错啦,请重试"; return(rtnResult); } } #endregion #region 验证通过->下发ticket // 正确 -> code:0 下发票据 ticket TicketModel ticketModel = new TicketModel { IP = userIp, IsPass = true, TS = DateTimeHelper.NowTimeStamp13() }; string ticketJsonStr = JsonHelper.Serialize(ticketModel); // 对 ticketJsonStr 加密 string ticket = AesHelper.EncryptEcbMode(ticketJsonStr, _options.AesKey); // 内存中存一份ticket, 用于效验 _cacheHelper.Insert <string>(CachePrefixTicket + verifyInfo.UserId, ticket); rtnResult.code = 0; rtnResult.message = "验证通过"; rtnResult.data = new VCodeCheckResponseModel.DataModel { appId = verifyInfo.AppId, ticket = ticket }; return(rtnResult); #endregion }
/// <summary> /// ticket效验 /// </summary> /// <param name="appId"></param> /// <param name="appSecret"></param> /// <param name="ticket"></param> /// <param name="userId">用户唯一标识</param> /// <param name="userIp"></param> /// <param name="aesKey"></param> /// <returns></returns> public TicketVerifyResponseModel TicketVerify(string appId, string appSecret, string ticket, string userId, string userIp) { TicketVerifyResponseModel rtnResult = null; // 解密ticket -> 转为实体对象 TicketModel ticketModel = null; try { string ticketJsonStr = AesHelper.DecryptEcbMode(ticket, _options.AesKey); // TODO: fixed: 临时修复, 直接将全部为0的字节去除, byte[] bytes = Encoding.UTF8.GetBytes(ticketJsonStr); byte[] remove0Bytes = bytes.Where(m => m != 0).ToArray(); string remove0ByteStr = Encoding.UTF8.GetString(remove0Bytes); // 能够转换为 对象, 则说明 vCodeKey 无误, 可以使用 ticketModel = JsonHelper.Deserialize <TicketModel>(remove0ByteStr); //ticketModel = JsonHelper.Deserialize<TicketModel>(ticketJsonStr); } catch (Exception ex) { // TODO: AES加解密后多出0, 导致无法转为json对象, 和验证码效验时一样 // '0x00' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 110. } if (ticketModel == null) { // ticket无效,被篡改 rtnResult = new TicketVerifyResponseModel { code = -4, message = "ticket无效" }; return(rtnResult); } // 从内存中取出此用户会话保存的独有Ticket,进行比对 string cacheKeyTicket = CachePrefixTicket + userId; if (!_cacheHelper.Exists(cacheKeyTicket)) { // ticket无效,1.此ticket已被效验过一次,用完销毁 2.其它原因: 伪造ticket rtnResult = new TicketVerifyResponseModel { code = -5, message = "ticket无效" }; return(rtnResult); } string rightTicket = _cacheHelper.Get(cacheKeyTicket).ToString(); if (ticket != rightTicket) { // ticket无效,1.篡改ticket rtnResult = new TicketVerifyResponseModel { code = -6, message = "ticket无效" }; RemoveCacheTicket(userId); return(rtnResult); } if (!ticketModel.IsPass) { // ticket 标识 验证不通过 rtnResult = new TicketVerifyResponseModel { code = -1, message = "验证不通过" }; RemoveCacheTicket(userId); return(rtnResult); } int secOffset = (int)((DateTimeHelper.NowTimeStamp13() - ticketModel.TS) / 1000); if (secOffset > _options.ExpiredSec) { // ticket 已过期 rtnResult = new TicketVerifyResponseModel { code = -2, message = "ticket过期" }; RemoveCacheTicket(userId); return(rtnResult); } if (ticketModel.IP != userIp) { // ip不匹配 rtnResult = new TicketVerifyResponseModel { code = -3, message = "ip不匹配" }; RemoveCacheTicket(userId); return(rtnResult); } // 验证通过 rtnResult = new TicketVerifyResponseModel { code = 0, message = "验证通过" }; RemoveCacheTicket(userId); return(rtnResult); }