從構建分散式秒殺系統聊聊驗證碼

来源:https://www.cnblogs.com/smallSevens/archive/2018/09/28/9714708.html
-Advertisement-
Play Games

前言 為了攔截大部分請求,秒殺案例前端引入了驗證碼。淘寶上很多人吐槽,等輸入完秒殺活動結束了,對,結束了...... 當然了,驗證碼的真正作用是,有效攔截刷單操作,讓羊毛黨空手而歸。 驗證碼 那麼到底什麼是驗證碼呢?驗證碼作為一種人機識別手段,其終極目的,就是區分正常人和機器的操作。我們常見的互聯網 ...


前言

為了攔截大部分請求,秒殺案例前端引入了驗證碼。淘寶上很多人吐槽,等輸入完秒殺活動結束了,對,結束了...... 當然了,驗證碼的真正作用是,有效攔截刷單操作,讓羊毛黨空手而歸。

驗證碼

那麼到底什麼是驗證碼呢?驗證碼作為一種人機識別手段,其終極目的,就是區分正常人和機器的操作。我們常見的互聯網註冊、登錄、發帖、領優惠券、投票等等應用場景,都有被機器刷造成各類損失的風險。

目前常見的驗證碼形式多為圖片驗證碼,即數字、字母、文字、圖片物體等形式的傳統字元驗證碼。這類驗證碼看似簡單易操作,但實際用戶體驗較差(參見12306網站),且隨著OCR技術和打碼平臺的利用,圖片比較容易被破解,被破解之後就形同虛設。

這裡我們使用騰訊的智能人機安全驗證碼,告別傳統驗證碼的單點防禦,十道安全柵欄打造立體全面的安全驗證,將黑產拒之門外。

場景

下麵我們來瞅瞅驗證碼輕鬆解決了那些場景安全問題:

  • 登錄註冊,為你防護撞庫攻擊、阻止註冊機批量註冊
  • 活動秒殺,有效攔截刷單操作,讓羊毛黨空手而歸
  • 點贊發帖,有效解決廣告屠版、惡意灌水、刷票問題
  • 數據保護,防止自動機、爬蟲盜取網頁內容和數據

申請

申請地址:https://007.qq.com/product.html

線上體驗:https://007.qq.com/online.html

只要一個QQ就可以免費申請,對於一般的企業OA系統或者個人博客網站,驗證碼免費套餐足夠了已經,具備以下特點:

  • 2000次/小時安全防護
  • 支持免驗證+分級驗證
  • 三分鐘快速接入
  • 全功能配置後臺
  • 支持HTTPS
  • 閾值內流量無廣告

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 簡稱 js防連點 var flag = true; $(".yzm>span").click(function(){ if(!flag){ return false } flag = false; var time = 60; var timer = setInterval(function(){ ...
  • 前段時間,公司在項目上用到了xhEditor編輯器來給用戶做一個上傳圖片的功能當時做的時候覺得很有意思,想想 基本的用戶圖片上傳到自己伺服器,還有點小占地方; 後來....然後直接上傳到阿裡雲 。接下來就是基本操作: 首先,引入官方提供的js庫 註:xhEditor插件下載官網:https://xh ...
  • TSAD的來源: TSAD由Open-test、Open-stor、Open-api、Open-dev四大系統組成,提供API測試平臺Open-test;測試通過版本可發佈服務倉庫Open-stor,倉庫抽離單一服務,其他產品部可任意裝配服務;根據業務需求將服務開放到Open-api與Open-de ...
  • 教程:高能:語句結構都是由關鍵字開頭,用冒號結束! 一:語句結構for <variable> in <sequence>: <statements>else: # else可有可無 <statements>二:基本規則 (1)使用縮進來劃分語句塊,相同縮進數的語句在一起組成一個語句塊。 (2)seq ...
  • ![](https://img2018.cnblogs.com/blog/711958/201809/711958-20180928091826555-1354813331.jpg) ...
  • ![](https://img2018.cnblogs.com/blog/711958/201809/711958-20180928091434379-573436458.jpg) ...
  • 原文出自: "http://cmsblogs.com" import 標簽解析完畢了,再看 Spring 中最複雜也是最重要的標簽 bean 標簽的解析過程。 在方法 中,如果遇到標簽 為 bean 則調用 方法進行 bean 標簽解析,如下: 整個過程分為四個步驟 1. 調用 進行元素解析,解析過 ...
  • 前言 此系列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什麼事。如果大家對springboot的源碼有所研究,可以挑些自己感興趣或者對自己有幫助的看;但是如果大家沒有研究過springboot的源碼,不知道springboot在啟動過程中做了些 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...