jQuery + SpringMVC 集成極驗驗證碼插件

極驗有一款行為驗證的插件拆魏,其實就是個驗證碼插件气筋,包括滑塊和點選的驗證方式缝驳,這里記錄一下如何接入基于 jQuery + SpringMVC 的 Web 端項目

更多精彩

寫在前面的話

  1. 接入這個行為驗證是因為自己網(wǎng)站的注冊驗證碼短信接口被人攻擊了归苍,所以就打算在發(fā)送注冊驗證碼之前加一個驗證
  2. 網(wǎng)上搜索一番看到極驗的這個插件,發(fā)現(xiàn)還不少公司用的运怖,像經(jīng)常去的 B 站霜医,和原來偶爾會逛得數(shù)字尾巴都有用
  3. 這個插件本身效果也還蠻不錯的,所以就選擇接入
  4. 插件本身提供免費試用驳规,只是有幾個限制,免費版只提供滑塊驗證署海,以及每小時最高 500 次的交互量吗购,咱們這小網(wǎng)站感覺夠用了
  5. 不過昨天晚上市場那邊正好在做一個推廣活動,突然給我打電話說用戶反饋注冊時候無法通過行為驗證
  6. 我上去一看砸狞,行為驗證報錯了捻勉,問了下他們多少人在注冊,那邊說就 200 個人刀森,我想的是這應(yīng)該也達不到 500 次的峰值
  7. 這得一個人驗證錯 3 次踱启,才能達到 600 次,所以我就體所應(yīng)當(dāng)?shù)囊詾槭呛蠖斯蛄搜械祝鋵崨]有
  8. 后來去極驗的后端一看埠偿,竟然交互量達到了 636 次,超過了峰值榜晦,所以接口返回為空了冠蒋,汗顏
  9. 既然市場這么給力,那我今天就聯(lián)系一下極驗的客服問下升級套餐唄乾胶,結(jié)果問完之后我就開始著手下線這個功能了
  10. 他們的價格差不多是這樣的抖剿,也不復(fù)雜,默認套餐價格區(qū)間是 6-20w 一年 识窿,6w 是支持每小時 1w 次的峰值交互量
  11. 我說 1w 都多了斩郎,我目前只需要 2000 次,那邊給了一個特惠套餐喻频,3w 一年缩宜,每小時 5000 次的峰值交互量
  12. 我順便問了一下,這個 3w 一年只僅僅這個插件的授權(quán)使用半抱,還是他們幾個產(chǎn)品都能用(他們貌似還有其他幾個產(chǎn)品脓恕,雖然并不感興趣)
  13. 那邊說只是驗證碼,這個我還真的比較汗顏窿侈,覺得他們這相當(dāng)于一個接口賣 3w 一年了炼幔,不過其實應(yīng)該是我酸,科科
  14. 總之這里只是記錄一下我的接入流程史简,因為接下來我準(zhǔn)備刪掉這個功能換成別的實現(xiàn)方式了
  15. 我現(xiàn)在還在想昨天 200 個人的注冊真的可以達到 600+ 次的交互量嗎乃秀,在有人引導(dǎo)注冊的前提下肛著,該不會是 …. ?

相關(guān)網(wǎng)址

  1. 行為驗證-Captcha-驗證碼-Captcha-極驗行為驗證
  2. geetest-java
  3. geetest-WEB-front

后端對接實現(xiàn)

下載并接入集成包

  1. 在前面提供的 geetest-java 中可以下載到插件后端環(huán)境的集成包跺讯,也可以通過 git clone https://github.com/GeeTeam/gt3-java-sdk.git 直接下載
  2. 下載下來其實是 Java 語言的本地文檔枢贿、DEMO 、SDK 刀脏、以及和后端語言配對的前端依賴 JS
    • 我覺得這里后端如果能提供一個在線的 Maven 或 Gradle 依賴地址其實更好
  3. /src/sdk/GeetestLib.javasrc/demo/demo1/GeetestConfig.java 引入到自己項目即可
  4. 打開 GeetestConfig.java 局荚,將其中的 geetest_idgeetest_key 修改成自己的即可
    • 這兩個參數(shù)需要到他們 極驗后臺登錄 獲取,登錄后選擇行為驗證
    • 之后還需要新增驗證愈污,填寫基本信息后即可拿到這兩個值耀态,如下圖
    • 左下角的 ID 對應(yīng) geetest_id ,KEY 對應(yīng) geetest_key
      image

編寫驗證碼服務(wù)

  1. 后端驗證的邏輯分為兩步驗證暂雹,第一步初始化首装,其實就是相當(dāng)于生成驗證碼,第二步才是用戶發(fā)起驗證請求
  2. 文檔中分別使用了 doGet 演示初始化杭跪,doPost 演示接收驗證請求
  3. 實際項目中當(dāng)然不會使用 Servlet 來做這些事情仙逻,這里使用的是 SpringMVC
  4. 兩個操作可以放在同一個 Service 中,例如 CaptchaServiceImpl.java

封裝請求參數(shù)

  1. 單獨封裝起來是因為兩步驗證都會用到
  2. 請求參數(shù)其實就是每個用戶的唯一標(biāo)識涧尿,文檔中使用的是 用戶 ID 系奉、終端類型 以及 IP 地址
  3. 我這里因為是未登錄狀態(tài)下的驗證,所以拿不到用戶 ID 姑廉,所以直接使用的 IP 地址
private HashMap<String, String> getParams() {
    String clientIp = URIUtil.getClientIp(request);

    HashMap<String, String> params = Maps.newHashMap();
    params.put("user_id", clientIp);
    params.put("client_type", "web");
    params.put("ip_address", clientIp);

    return params;
}

生成驗證碼喜最,對應(yīng)第一步初始化

  1. getParams() 獲取參數(shù)就是上一個函數(shù)
public String generateCaptcha() {
    // 初始化極驗服務(wù)
    GeetestLib lib = new GeetestLib(GeetestConfig.getGeetest_id(), GeetestConfig.getGeetest_key(), GeetestConfig.isnewfailback());

    // 驗證預(yù)處理
    int status = lib.preProcess(getParams());

    // 將服務(wù)狀態(tài)存放到 Session 中,在第二步驗證時會用到
    request.getSession().setAttribute(lib.gtServerStatusSessionKey, status);

    // 返回生成字串
    return lib.getResponseStr();
}

接收用戶驗證請求庄蹋,并返回存儲處理結(jié)果

  1. request.getParameter() 可以直接使用瞬内,是因為我的 Service 集成了一個通用的父類,在里面直接注入了 HttpServletRequest
  2. 通過 request 獲取的參數(shù)都不是自定義的限书,由極驗前端的 gt.js 內(nèi)部提供
  3. saveMessage() 是驗證成功后將當(dāng)前參與驗證的手機號和結(jié)果存儲到 Redis 中虫蝶,時效 5 分鐘
    • 通過行為驗證,正式發(fā)起驗證碼請求時倦西,會先通過請求的手機號去 Redis 中獲取行為驗證結(jié)果
    • 存在且成功才會發(fā)驗證碼能真,否則會提示進行行為驗證
  4. TSharkException() 是自定義的異常處理,由 Controller 捕獲后拋到前端彈出提示框告知用戶
public void checkCaptcha(String mobile) {
    // 初始化極驗服務(wù)
    GeetestLib lib = new GeetestLib(GeetestConfig.getGeetest_id(), GeetestConfig.getGeetest_key(), GeetestConfig.isnewfailback());

    // 接收前端參數(shù)扰柠,由前端 JS 內(nèi)部封裝處理
    String challenge = request.getParameter(GeetestLib.fn_geetest_challenge);
    String validate = request.getParameter(GeetestLib.fn_geetest_validate);
    String seccode = request.getParameter(GeetestLib.fn_geetest_seccode);

    // 取出第一步初始化驗證時存儲的服務(wù)狀態(tài)
    int status = (Integer) request.getSession().getAttribute(lib.gtServerStatusSessionKey);

    int result = 0;

    if (status == 1) {
        // 服務(wù)器在線
        result = lib.enhencedValidateRequest(challenge, validate, seccode, getParams());
    } else {
        // 服務(wù)器離線
        result = lib.failbackValidateRequest(challenge, validate, seccode);
    }

    if (result == 1) {
        // 保存驗證信息
        saveMessage(mobile, new MessageBean(mobile, result));
    } else {
        throw new TSharkException("行為驗證失敗粉铐,請檢查使用環(huán)境");
    }
}

控制層定義請求

  1. 因為是傳統(tǒng)項目(非前后分離),所以使用的注解依舊是以下方式
    • 請求相同卤档,分別使用 RequestMethod.GETRequestMethod.POST 來區(qū)分第一步驗證和第二步驗證
  2. SimpleActionHandler 是自定義封裝的 Service 異常處理類
    • request 在繼承的 AbstractBaseController 自定義父類中統(tǒng)一聲明
  3. ResponseData 是自定義封裝的返回結(jié)果類
  4. 后端的服務(wù)就完成了蝙泼,實現(xiàn)起來還是非常簡潔的,點個贊
@Controller
@RequestMapping("/captcha")
public class CaptchaController extends AbstractBaseController {

    @Autowired
    private CaptchaServiceImpl captchaService;

    @RequestMapping(value = "", method = RequestMethod.GET)
    @ResponseBody
    public ResponseData init() {
        return new SimpleActionHandler(request) {
            @Override
            protected void doHandle(ResponseData responseData) throws Exception {
                responseData.setData(captchaService.generateCaptcha());
            }
        }.handle();
    }

    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseBody
    public ResponseData check(@RequestParam final String mobile) {
        return new SimpleActionHandler(request) {
            @Override
            protected void doHandle(ResponseData responseData) throws Exception {
                captchaService.checkCaptcha(mobile);
            }
        }.handle();
    }

}

前端對接實現(xiàn)

前端依賴 JS 在哪里

  1. 前端依賴的 JS 在 geetest-WEB-front 接口文檔中是找不到的劝枣,因為不同服務(wù)端語言對應(yīng)的 JS 不一樣汤踏,所以將其放置在了后端集成包中
  2. 具體位置如下圖所示


    image
  3. 官方也給了相關(guān)提示织鲸,如下圖


    image

引入前端依賴 JS

  1. 將 JS 加入到項目并引入即可
  2. 因為我的項目里用到了 jQuery ,所以以下代碼中先引入了
<script type="text/javascript" src="${ctx }/assets/plugins/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="${ctx }/assets/plugins/geetest/gt.js"></script>

準(zhǔn)備一個 HTML 區(qū)域用于顯示行為驗證組件

  1. 具體樣式因人而異溪胶,總之就是提供一個 div 用于放置初始化好的驗證組件即可
<section>
  <label class="label">行為驗證 <span class="text-danger pull-right"></span></label>
  <label class="input captcha">
    <div class="captcha-tip">行為驗證加載中</div>
  </label>
</section>

初始化行為驗證插件

  1. 插件的初始化函數(shù)是 initGeetest() 搂擦,來自上文中的 gt.js
  2. 但其實在初始化之前需要先發(fā)送一個異步請求,去調(diào)用服務(wù)端的初始化接口哗脖,因為初始化函數(shù)中的值瀑踢,是由服務(wù)端初始化接口返回的
  3. 初始化成功后在回調(diào)函數(shù)中通過 captchaObj.appendTo() 將行為驗證組件加入指定的頁面容器中
  4. 因為組件初始化需要先訪問后端接口,考慮到網(wǎng)絡(luò)延遲才避,所以一般會先給個提示丘损,比如顯示一句話 “行為驗證加載中”
    • 如果要在初始化結(jié)束后隱藏,在 captchaObj.onReady() 中調(diào)用即可工扎,這些官方文檔都有介紹
// 發(fā)起第一步驗證
$.ajax({
  url: '${ctx}/captcha',
  type: 'GET',
  dataType: 'json',
  success: function (data) {
    var result = JSON.parse(data.data)

    // 初始化行為驗證
    initGeetest({
      gt: result.gt,
      challenge: result.challenge,
      new_captcha: result.new_captcha,
      offline: !result.success,
      product: 'float',
      width: '100%'
    }, function (captchaObj) {
      // 顯示行為驗證操作元素
      captchaObj.appendTo($memberRegisterPanel.find('.captcha'))

      captchaObj.onReady(function () {
        getCurrentPanel().find('.captcha-tip').hide()
      })

        // .. 執(zhí)行后續(xù)操作
    })
  }
})

用戶前端驗證成功后,發(fā)起后端核驗請求

  1. 這一步的操作會在用戶進行滑塊驗證并成功后被調(diào)用
  2. 如果滑塊都沒有弄對衔蹲,則不會發(fā)起后端核驗
  3. 這里發(fā)起的驗證實際上就是去調(diào)用后端的第二步驗證接口
    • mobile 是傳入的手機號
    • 其他的三個參數(shù)都是極驗插件內(nèi)部返回的驗證結(jié)果肢娘,用于發(fā)送到后端進行二次核驗
  4. 在請求的回調(diào)中可以判斷核驗結(jié)果,并執(zhí)行后續(xù)操作
captchaObj.onSuccess(function () {
  var result = captchaObj.getValidate()
  
    // 發(fā)起之前可以先進行一些自定義驗證舆驶,比如手機號是否填寫橱健,格式是否錯誤
    // 如果自定義驗證沒有通過,可以使用 captchaObj.reset() 重置驗證組件狀態(tài)

  // 發(fā)起二次驗證
  $.ajax({
    url: '${ctx}/captcha',
    type: 'POST',
    data: {
      mobile: mobileVal,
      geetest_challenge: result.geetest_challenge,
      geetest_validate: result.geetest_validate,
      geetest_seccode: result.geetest_seccode
    },
    dataType: 'json',
    success: function (data) {
      if (!data.success) {
        // 驗證成功后的具體操作
      }
    }
  })
})

一句話總結(jié)

  1. 極驗的這個行為驗證組件接入的方式比較簡單沙廉,實現(xiàn)的效果也蠻不錯的拘荡,但我還是認為就一個驗證碼功能一年收費 6w 起,太貴了撬陵,搞不起搞不起珊皿,科科
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市巨税,隨后出現(xiàn)的幾起案子蟋定,更是在濱河造成了極大的恐慌草添,老刑警劉巖驶兜,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異远寸,居然都是意外死亡抄淑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門驰后,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肆资,“玉大人,你說我怎么就攤上這事灶芝⊙冈牛” “怎么了贱枣?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颤专。 經(jīng)常有香客問我纽哥,道長,這世上最難降的妖魔是什么栖秕? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任春塌,我火速辦了婚禮,結(jié)果婚禮上簇捍,老公的妹妹穿的比我還像新娘只壳。我一直安慰自己,他們只是感情好暑塑,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布吼句。 她就那樣靜靜地躺著,像睡著了一般事格。 火紅的嫁衣襯著肌膚如雪惕艳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天驹愚,我揣著相機與錄音远搪,去河邊找鬼。 笑死逢捺,一個胖子當(dāng)著我的面吹牛谁鳍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播劫瞳,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼倘潜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了志于?” 一聲冷哼從身側(cè)響起窍荧,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恨憎,沒想到半個月后蕊退,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡憔恳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年瓤荔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钥组。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡输硝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出程梦,到底是詐尸還是另有隱情点把,我是刑警寧澤橘荠,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站郎逃,受9級特大地震影響哥童,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜褒翰,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一贮懈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧优训,春花似錦朵你、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至早敬,卻和暖如春忌傻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搁嗓。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留箱靴,地道東北人腺逛。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像衡怀,于是被迫代替她去往敵國和親棍矛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容