极验-滑块验证
主要分为两大步
一、前端部署
(1)引入jQuerry.js和gt.js文件(gt.js文件就是极验必须的文件)如下
/* initGeetest 1.0.0 * 用于加载id对应的验证码库,并支持宕机模式 * 暴露 initGeetest 进行验证码的初始化 * 一般不需要用户进行修改 */ (function (global, factory) { "use strict"; if (typeof module === "object" && typeof module.exports === "object") { // CommonJS module.exports = global.document ? factory(global, true) : function (w) { if (!w.document) { throw new Error("Geetest requires a window with a document"); } return factory(w); }; } else { factory(global); } })(typeof window !== "undefined" ? window : this, function (window, noGlobal) { "use strict"; if (typeof window === 'undefined') { throw new Error('Geetest requires browser environment'); } var document = window.document; var Math = window.Math; var head = document.getElementsByTagName("head")[0]; function _Object(obj) { this._obj = obj; } _Object.prototype = { _each: function (process) { var _obj = this._obj; for (var k in _obj) { if (_obj.hasOwnProperty(k)) { process(k, _obj[k]); } } return this; } }; function Config(config) { var self = this; new _Object(config)._each(function (key, value) { self[key] = value; }); } Config.prototype = { api_server: 'api.geetest.com', protocol: 'http://', type_path: '/gettype.php', fallback_config: { slide: { static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], type: 'slide', slide: '/static/js/geetest.0.0.0.js' }, fullpage: { static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], type: 'fullpage', fullpage: '/static/js/fullpage.0.0.0.js' } }, _get_fallback_config: function () { var self = this; if (isString(self.type)) { return self.fallback_config[self.type]; } else if (self.new_captcha) { return self.fallback_config.fullpage; } else { return self.fallback_config.slide; } }, _extend: function (obj) { var self = this; new _Object(obj)._each(function (key, value) { self[key] = value; }) } }; var isNumber = function (value) { return (typeof value === 'number'); }; var isString = function (value) { return (typeof value === 'string'); }; var isBoolean = function (value) { return (typeof value === 'boolean'); }; var isObject = function (value) { return (typeof value === 'object' && value !== null); }; var isFunction = function (value) { return (typeof value === 'function'); }; var callbacks = {}; var status = {}; var random = function () { return parseInt(Math.random() * 10000) + (new Date()).valueOf(); }; var loadScript = function (url, cb) { var script = document.createElement("script"); script.charset = "UTF-8"; script.async = true; script.onerror = function () { cb(true); }; var loaded = false; script.onload = script.onreadystatechange = function () { if (!loaded && (!script.readyState || "loaded" === script.readyState || "complete" === script.readyState)) { loaded = true; setTimeout(function () { cb(false); }, 0); } }; script.src = url; head.appendChild(script); }; var normalizeDomain = function (domain) { return domain.replace(/^https?:\/\/|\/$/g, ''); }; var normalizePath = function (path) { path = path.replace(/\/+/g, '/'); if (path.indexOf('/') !== 0) { path = '/' + path; } return path; }; var normalizeQuery = function (query) { if (!query) { return ''; } var q = '?'; new _Object(query)._each(function (key, value) { if (isString(value) || isNumber(value) || isBoolean(value)) { q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; } }); if (q === '?') { q = ''; } return q.replace(/&$/, ''); }; var makeURL = function (protocol, domain, path, query) { domain = normalizeDomain(domain); var url = normalizePath(path) + normalizeQuery(query); if (domain) { url = protocol + domain + url; } return url; }; var load = function (protocol, domains, path, query, cb) { var tryRequest = function (at) { var url = makeURL(protocol, domains[at], path, query); loadScript(url, function (err) { if (err) { if (at >= domains.length - 1) { cb(true); } else { tryRequest(at + 1); } } else { cb(false); } }); }; tryRequest(0); }; var jsonp = function (domains, path, config, callback) { if (isObject(config.getLib)) { config._extend(config.getLib); callback(config); return; } if (config.offline) { callback(config._get_fallback_config()); return; } var cb = "geetest_" + random(); window[cb] = function (data) { if (data.status === 'success') { callback(data.data); } else if (!data.status) { callback(data); } else { callback(config._get_fallback_config()); } window[cb] = undefined; try { delete window[cb]; } catch (e) { } }; load(config.protocol, domains, path, { gt: config.gt, callback: cb }, function (err) { if (err) { callback(config._get_fallback_config()); } }); }; var throwError = function (errorType, config) { var errors = { networkError: '网络错误' }; if (typeof config.onError === 'function') { config.onError(errors[errorType]); } else { throw new Error(errors[errorType]); } }; var detect = function () { return !!window.Geetest; }; if (detect()) { status.slide = "loaded"; } var initGeetest = function (userConfig, callback) { var config = new Config(userConfig); if (userConfig.https) { config.protocol = 'https://'; } else if (!userConfig.protocol) { config.protocol = window.location.protocol + '//'; } jsonp([config.api_server || config.apiserver], config.type_path, config, function (newConfig) { var type = newConfig.type; var init = function () { config._extend(newConfig); callback(new window.Geetest(config)); }; callbacks[type] = callbacks[type] || []; var s = status[type] || 'init'; if (s === 'init') { status[type] = 'loading'; callbacks[type].push(init); load(config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { if (err) { status[type] = 'fail'; throwError('networkError', config); } else { status[type] = 'loaded'; var cbs = callbacks[type]; for (var i = 0, len = cbs.length; i < len; i = i + 1) { var cb = cbs[i]; if (isFunction(cb)) { cb(); } } callbacks[type] = []; } }); } else if (s === "loaded") { init(); } else if (s === "fail") { throwError('networkError', config); } else if (s === "loading") { callbacks[type].push(init); } }); }; window.initGeetest = initGeetest; return initGeetest; });
当然这是我用的文件,可能有些地方改动了,也可重新去官网下载一份gt.js
(2)需要验证的页面加入如下JS和css样式
<style type="text/css"> div#embed-captcha {padding-top:20px;} .show {display:block;} .hide {display:none;} </style> <script> var handlerEmbed = function (captchaObj) { $("#embed-submit").click(function (e) { var validate = captchaObj.getValidate(); if (!validate) { $("#notice")[0].className = "show"; setTimeout(function () { $("#notice")[0].className = "hide"; }, 2000); e.preventDefault(); } }); // 将验证码加到id为captcha的元素里,同时会有三个input的值:geetest_challenge, geetest_validate, geetest_seccode captchaObj.appendTo("#embed-captcha"); captchaObj.onReady(function () { $("#wait")[0].className = "hide"; }); }; $.ajax({ // 获取id,challenge,success(是否启用failback) url: "/index.php?m=member&c=index&a=gcheck", // 加随机数防止缓存 type: "get", dataType: "json", success: function (data) { // 使用initGeetest接口 // 参数1:配置参数 // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件 initGeetest({ gt: data.gt, challenge: data.challenge, new_captcha: data.new_captcha, product: "embed", // 产品形式,包括:float,embed,popup。注意只对PC版验证码有效 offline: !data.success // 表示用户后台检测极验服务器是否宕机,一般不需要关注 }, handlerEmbed); } }); </script>
(3)在需要的地方加入div
<div id="embed-captcha"> <p id="wait" class="show">正在加载验证码......</p> <p id="notice" class="hide">请先完成验证</p> </div>
这时候前段页面已经布好局了,但是此时时还不能看到效果,因为有一部分文件是通过js加载进去的。
二、服务端部署
接下来部署服务器端,以我用的phpcms为例
(1)首先在类库里加入Geelib.class.php文件
<?php /** * 极验行为式验证安全平台,php 网站主后台包含的库文件 * * @author Tanxu */ define("CAPTCHA_ID", "注册之后得到的id"); define("PRIVATE_KEY", "注册之后得到的key"); class GeeLib { const GT_SDK_VERSION = 'php_3.0.0'; public static $connectTimeout = 1; public static $socketTimeout = 1; private $response; public function __construct($captcha_id, $private_key) { $this->captcha_id = $captcha_id; $this->private_key = $private_key; } /** * 判断极验服务器是否down机 * * @param array $data * @return int */ public function pre_process($param, $new_captcha=1) { $data = array('gt'=>$this->captcha_id, 'new_captcha'=>$new_captcha ); $data = array_merge($data,$param); $query = http_build_query($data); $url = "http://api.geetest.com/register.php?" . $query; $challenge = $this->send_request($url); if (strlen($challenge) != 32) { $this->failback_process(); return 0; } $this->success_process($challenge); return 1; } /** * @param $challenge */ private function success_process($challenge) { $challenge = md5($challenge . $this->private_key); $result = array( 'success' => 1, 'gt' => $this->captcha_id, 'challenge' => $challenge, 'new_captcha'=>1 ); $this->response = $result; } /** * */ private function failback_process() { $rnd1 = md5(rand(0, 100)); $rnd2 = md5(rand(0, 100)); $challenge = $rnd1 . substr($rnd2, 0, 2); $result = array( 'success' => 0, 'gt' => $this->captcha_id, 'challenge' => $challenge, 'new_captcha'=>1 ); $this->response = $result; } /** * @return mixed */ public function get_response_str() { return json_encode($this->response); } /** * 返回数组方便扩展 * * @return mixed */ public function get_response() { return $this->response; } /** * 正常模式获取验证结果 * * @param string $challenge * @param string $validate * @param string $seccode * @param array $param * @return int */ public function success_validate($challenge, $validate, $seccode,$param, $json_format=1) { if (!$this->check_validate($challenge, $validate)) { return 0; } $query = array( "seccode" => $seccode, "timestamp"=>time(), "challenge"=>$challenge, "captchaid"=>$this->captcha_id, "json_format"=>$json_format, "sdk" => self::GT_SDK_VERSION ); $query = array_merge($query,$param); $url = "http://api.geetest.com/validate.php"; $codevalidate = $this->post_request($url, $query); $obj = json_decode($codevalidate,true); if ($obj === false){ return 0; } if ($obj['seccode'] == md5($seccode)) { return 1; } else { return 0; } } /** * 宕机模式获取验证结果 * * @param $challenge * @param $validate * @param $seccode * @return int */ public function fail_validate($challenge, $validate, $seccode) { if(md5($challenge) == $validate){ return 1; }else{ return 0; } } /** * @param $challenge * @param $validate * @return bool */ private function check_validate($challenge, $validate) { if (strlen($validate) != 32) { return false; } if (md5($this->private_key . 'geetest' . $challenge) != $validate) { return false; } return true; } /** * GET 请求 * * @param $url * @return mixed|string */ private function send_request($url) { if (function_exists('curl_exec')) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout); curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $curl_errno = curl_errno($ch); $data = curl_exec($ch); curl_close($ch); if ($curl_errno >0) { return 0; }else{ return $data; } } else { $opts = array( 'http' => array( 'method' => "GET", 'timeout' => self::$connectTimeout + self::$socketTimeout, ) ); $context = stream_context_create($opts); $data = @file_get_contents($url, false, $context); if($data){ return $data; }else{ return 0; } } } /** * * @param $url * @param array $postdata * @return mixed|string */ private function post_request($url, $postdata = '') { if (!$postdata) { return false; } $data = http_build_query($postdata); if (function_exists('curl_exec')) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout); curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout); //不可能执行到的代码 if (!$postdata) { curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); } else { curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); } $data = curl_exec($ch); if (curl_errno($ch)) { $err = sprintf("curl[%s] error[%s]", $url, curl_errno($ch) . ':' . curl_error($ch)); $this->triggerError($err); } curl_close($ch); } else { if ($postdata) { $opts = array( 'http' => array( 'method' => 'POST', 'header' => "Content-type: application/x-www-form-urlencoded\r\n" . "Content-Length: " . strlen($data) . "\r\n", 'content' => $data, 'timeout' => self::$connectTimeout + self::$socketTimeout ) ); $context = stream_context_create($opts); $data = file_get_contents($url, false, $context); } } return $data; } /** * @param $err */ private function triggerError($err) { trigger_error($err); } }
(2)在前端部署第二步有个ajax请求地址,在改地址指向的文件夹里写函数,判断极验服务器是否down机
public function gcheck(){ $this->_session_start(); $GtSdk = new GeeLib(CAPTCHA_ID, PRIVATE_KEY); $data = array( "user_id" => "test", # 网站用户id "client_type" => "web", #web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生SDK植入APP应用的方式 "ip_address" => ip() # 请在此处传输用户请求验证时所携带的IP ); $status = $GtSdk->pre_process($data, 1); $_SESSION['gtserver']= $status; $_SESSION['user_id']= $data['user_id']; echo $GtSdk->get_response_str(); exit; }
(3)在注册方法里写服务器端验证
$geeResult = false; $GtSdk = new GeeLib(CAPTCHA_ID, PRIVATE_KEY); $serverStatus = $_SESSION['gtserver']; $data = array( "user_id" => "test_XXX", # 网站用户id "client_type" => "web", #web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生SDK植入APP应用的方式 "ip_address" => ip() # 请在此处传输用户请求验证时所携带的IP ); if ($serverStatus == 1) { //服务器正常 $geeResult = $GtSdk->success_validate($_POST['geetest_challenge'], $_POST['geetest_validate'], $_POST['geetest_seccode'], $data); }else{ //服务器宕机,走failback模式 $geeResult = $GtSdk->fail_validate($_POST['geetest_challenge'],$_POST['geetest_validate'],$_POST['geetest_seccode']); } if(!$geeResult){ showmessage('验证码错误', 'index.php?m=member&c=index&a=register'); }
至此完成!