爲了攔截大部分請求,秒殺案例前端引入了驗證碼。淘寶上不少人吐槽,等輸入完秒殺活動結束了,對,結束了...... 固然了,驗證碼的真正做用是,有效攔截刷單操做,讓羊毛黨空手而歸。javascript
那麼到底什麼是驗證碼呢?驗證碼做爲一種人機識別手段,其終極目的,就是區分正常人和機器的操做。咱們常見的互聯網註冊、登陸、發帖、領優惠券、投票等等應用場景,都有被機器刷形成各種損失的風險。html
目前常見的驗證碼形式多爲圖片驗證碼,即數字、字母、文字、圖片物體等形式的傳統字符驗證碼。這類驗證碼看似簡單易操做,但實際用戶體驗較差(參見12306網站),且隨着OCR技術和打碼平臺的利用,圖片比較容易被破解,被破解以後就形同虛設。前端
這裏咱們使用騰訊的智能人機安全驗證碼,告別傳統驗證碼的單點防護,十道安全柵欄打造立體全面的安全驗證,將黑產拒之門外。java
下面咱們來瞅瞅驗證碼輕鬆解決了那些場景安全問題:git
申請地址:https://007.qq.com/product.htmlajax
在線體驗:https://007.qq.com/online.htmlspring
只要一個QQ就能夠免費申請,對於通常的企業OA系統或者我的博客網站,驗證碼免費套餐足夠了已經,具有如下特色:json
2000次/小時的安全防禦,通常不多達到如此效果,固然了即時超出閾值,頂多也就是多個廣告而已。後端
快讀接入:https://007.qq.com/quick-start.html安全
接入與幫助提供了多種客戶端和服務端的接入案例,這裏咱們使用咱們秒殺案例中最熟悉的Java語言來接入。
引入JS:
<script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>
頁面元素:
<!--點擊此元素會自動激活驗證碼,不必定是button,其餘標籤也能夠--> <!--id : 元素的id(必須)--> <!--data-appid : AppID(必須)--> <!--data-cbfn : 回調函數名(必須)--> <!--data-biz-state : 業務自定義透傳參數(可選)--> <button id="TencentCaptcha" data-appid="*********" data-cbfn="callback">驗證</button>
JS回調:
<script type="text/javascript"> window.callback = function(res){ console.log(res) // res(未經過驗證)= {ret: 1, ticket: null} // res(驗證成功) = {ret: 0, ticket: "String", randstr: "String"} if(res.ret === 0){ startSeckill(res) } } //後臺驗證ticket,並進入秒殺隊列 function startSeckill(res){ $.ajax({ url : "startSeckill", type : 'post', data : {'ticket' : res.ticket,'randstr':res.randstr}, success : function(result) { //驗證是否經過,提示用戶 } }); } </script>
@Api(tags = "秒殺商品") @RestController @RequestMapping("/seckillPage") public class SeckillPageController { @Autowired private ActiveMQSender activeMQSender; //自定義工具類 @Autowired private HttpClient httpClient; //這裏自行配置參數 @Value("${qq.captcha.url}") private String url; @Value("${qq.captcha.aid}") private String aid; @Value("${qq.captcha.AppSecretKey}") private String appSecretKey; @RequestMapping("/startSeckill") public Result startSeckill(String ticket,String randstr,HttpServletRequest request) { HttpMethod method =HttpMethod.POST; MultiValueMap<String, String> params= new LinkedMultiValueMap<String, String>(); params.add("aid", aid); params.add("AppSecretKey", appSecretKey); params.add("Ticket", ticket); params.add("Randstr", randstr); params.add("UserIP", IPUtils.getIpAddr(request)); String msg = httpClient.client(url,method,params); /** * response: 1:驗證成功,0:驗證失敗,100:AppSecretKey參數校驗錯誤[required] * evil_level:[0,100],惡意等級[optional] * err_msg:驗證錯誤信息[optional] */ //{"response":"1","evil_level":"0","err_msg":"OK"} JSONObject json = JSONObject.parseObject(msg); String response = (String) json.get("response"); if("1".equals(response)){ //進入隊列、假數據而已 Destination destination = new ActiveMQQueue("seckill.queue"); activeMQSender.sendChannelMess(destination,1000+";"+1); return Result.ok(); }else{ return Result.error("驗證失敗"); } } }
自定義請求工具類 HttpClient:
@Service public class HttpClient { public String client(String url, HttpMethod method, MultiValueMap<String, String> params){ RestTemplate client = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); // 請勿輕易改變此提交方式,大部分的狀況下,提交方式都是表單提交 headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(params, headers); // 執行HTTP請求 ResponseEntity<String> response = client.exchange(url, HttpMethod.POST, requestEntity, String.class); return response.getBody(); } }
獲取IP地址工具類 IPUtils :
/** * IP地址 */ public class IPUtils { private static Logger logger = LoggerFactory.getLogger(IPUtils.class); /** * 獲取IP地址 * 使用Nginx等反向代理軟件, 則不能經過request.getRemoteAddr()獲取IP地址 * 若是使用了多級反向代理的話,X-Forwarded-For的值並不止一個,而是一串IP地址,X-Forwarded-For中第一個非unknown的有效IP字符串,則爲真實IP地址 */ public static String getIpAddr(HttpServletRequest request) { String ip = null; try { ip = request.getHeader("x-forwarded-for"); if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } } catch (Exception e) { logger.error("IPUtils ERROR ", e); } // 使用代理,則獲取第一個IP地址 if (StringUtils.isEmpty(ip) && ip.length() > 15) { if (ip.indexOf(",") > 0) { ip = ip.substring(0, ip.indexOf(",")); } } return ip; } }
啓動項目訪問:http://localhost:8080/seckill/1000.shtml
在系統登陸的時候,咱們須要先校驗用戶名以及密碼,而後調用驗證碼操做,這裏就須要咱們定製接入了。
<!-- 項目中使用了Vue --> <div class="log_btn" @click="login" >登陸</div>
login: function () { //這裏校驗用戶名以及密碼 // 直接生成一個驗證碼對象 var captcha = new TencentCaptcha('2001344788', function(res) { if(res.ret === 0){//回調成功 var data = {'username':username,'password':password,'ticket':res.ticket,'randstr':res.randstr} $.ajax({ type: "POST", url: "sys/loginCaptcha", data: data, dataType: "json", success: function(result){ //校驗是否成功 } }); } }); captcha.show(); // 顯示驗證碼 },
騰訊後臺還提供了簡單實用的數據監控,以下:
整體來講,系統接入人機驗證碼仍是很方便的,並無技術難點,難點已經被提供商封裝,咱們只須要簡單的調用便可。
秒殺案例:https://gitee.com/52itstyle/spring-boot-seckill
演示案例(點擊生成按鈕):http://jichou.52itstyle.com