├── config └── config.php ├── composer.json ├── web ├── StartCaptchaServlet.php └── VerifyLoginServlet.php ├── README.md ├── static ├── login.html └── gt.js └── lib └── class.geetestlib.php /config/config.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gee-team/gt3-php-sdk", 3 | "description": "Gt3 Php Demo", 4 | "authors": [ 5 | { 6 | "name": "tanxu1993", 7 | "email": "tanxu1993@gmail.com" 8 | } 9 | ], 10 | "require": { 11 | "php": ">=5.0.0" 12 | }, 13 | "autoload": { 14 | "classmap": [ 15 | "lib" 16 | ] 17 | }, 18 | "license": "MIT" 19 | } -------------------------------------------------------------------------------- /web/StartCaptchaServlet.php: -------------------------------------------------------------------------------- 1 | "test", # 网站用户id 14 | "client_type" => "web", #web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生SDK植入APP应用的方式 15 | "ip_address" => "127.0.0.1" # 请在此处传输用户请求验证时所携带的IP 16 | ); 17 | 18 | $status = $GtSdk->pre_process($data, 1); 19 | $_SESSION['gtserver'] = $status; 20 | $_SESSION['user_id'] = $data['user_id']; 21 | echo $GtSdk->get_response_str(); 22 | ?> -------------------------------------------------------------------------------- /web/VerifyLoginServlet.php: -------------------------------------------------------------------------------- 1 | $_SESSION['user_id'], # 网站用户id 13 | "client_type" => "web", #web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生SDK植入APP应用的方式 14 | "ip_address" => "127.0.0.1" # 请在此处传输用户请求验证时所携带的IP 15 | ); 16 | 17 | 18 | if ($_SESSION['gtserver'] == 1) { //服务器正常 19 | $result = $GtSdk->success_validate($_POST['geetest_challenge'], $_POST['geetest_validate'], $_POST['geetest_seccode'], $data); 20 | if ($result) { 21 | echo '{"status":"success"}'; 22 | } else{ 23 | echo '{"status":"fail"}'; 24 | } 25 | }else{ //服务器宕机,走failback模式 26 | if ($GtSdk->fail_validate($_POST['geetest_challenge'],$_POST['geetest_validate'],$_POST['geetest_seccode'])) { 27 | echo '{"status":"success"}'; 28 | }else{ 29 | echo '{"status":"fail"}'; 30 | } 31 | } 32 | ?> 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 极验行为验证 2 | ======== 3 | 极验行为验证是一款可以帮助你的网站与 APP 应用识别与拦截机器程序批量自动化操作的SaaS应用。它是由极验开发的 4 | 新一代人机验证产品,它不基于传统“问题-答案”的检测模式,而是通过利用深度学习对验证过程中产生的行为数据进行 5 | 高维分析,发现人机行为模式与行为特征的差异,更加精准地区分人机行为。 6 | 7 | 8 | 集成流程 9 | -------- 10 | 行为验证的整个集成流程是顺序进行的,业务层主要涉及到客户端和服务端的部署,在下一个步骤开始前请确保上一个 11 | 步骤的检查点都已经正确完成;请开发者严格按照步骤进行。 12 | 13 | 步骤: 注册极验账户(1) - 登录极验后台(2) - 注册验证ID和Key (3) - 配置ID属性(4) - 集成服务端代码(5) - 集成客户端代码(6) - 服务上线(7) - 数据上线(8) - 登录后台查看数据(9) 14 | 15 | 16 | 新手指南 17 | -------- 18 | 0. 产品概述 - https://docs.geetest.com/install/overview/prodes/ 19 | 1. 入门指引 - https://docs.geetest.com/install/overview/beginner/ 20 | 21 | 22 | 文档导航 23 | -------- 24 | * 部署指引 - https://docs.geetest.com/install/overview/guide 25 | * 数据通讯流程 - https://docs.geetest.com/install/overview/flowchart 26 | * 服务的部署 - https://docs.geetest.com/install/deploy/server/php 27 | * 客户端部署 - https://docs.geetest.com/install/deploy/client/web 28 | * 名词解释 - https://docs.geetest.com/install/help/glossary 29 | * 常见问题 - https://docs.geetest.com/install/help/faq 30 | 31 | 32 | 联系我们 33 | -------- 34 | * 官网: www.geetest.com 35 | * 技术支持邮箱:service@geetest.com 36 | * 技术支持电话:400-8521-816 37 | * 联系商务邮箱:cooperation@geetest.com 38 | * 联系商务电话:13720157161 39 | 40 | 41 | Gt Php SDK 42 | =============== 43 | 使用 3.1 之前版本SDK的用户如果想更新到3.1以及以后版本请先联系极验客服,因为为了兼容老用户,新的特性需要修改验证设置 44 | 45 | **注意事项:部署在生产环境中时,需要将gt.js文件存放到项目中并在页面中引用该文件。该js的作用是充分利用多CDN,使静态文件尽可能加载成功。** 46 | 47 | 开发环境 48 | ---------------- 49 | 50 | - php5.2+ 及php7 51 | 52 | 文件说明 53 | --------------- 54 | - config/config.php 极验ID和KEY配置文件,请在[极验后台](http://account.geetest.com)申请,进行替换 55 | - lib/class.geetestlib.php 极验库文件(请不要随意改动) 56 | - static/login.php 前端展示页面,根据您的需求进行自定义 57 | - web/StartCaptchaServlet.php 根据自己的私钥初始化验证 58 | - web/VerifyLoginServlet.php 根据post参数进行二次验证 59 | 60 | 61 | 62 | 注意 63 | -------------- 64 | 注意前端参数中 new_captcha参数 -------------------------------------------------------------------------------- /static/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | gt-php-sdk-demo 7 | 40 | 41 | 42 |

极验验证SDKDemo

43 | 63 | 64 | 65 | 103 | 104 | -------------------------------------------------------------------------------- /lib/class.geetestlib.php: -------------------------------------------------------------------------------- 1 | captcha_id = $captcha_id; 18 | $this->private_key = $private_key; 19 | $this->domain = "http://api.geetest.com"; 20 | } 21 | 22 | /** 23 | * 判断极验服务器是否down机 24 | * 25 | * @param array $data 26 | * @return int 27 | */ 28 | public function pre_process($param, $new_captcha=1) { 29 | $data = array('gt'=>$this->captcha_id, 30 | 'new_captcha'=>$new_captcha 31 | ); 32 | $data = array_merge($data,$param); 33 | $query = http_build_query($data); 34 | $url = $this->domain . "/register.php?" . $query; 35 | $challenge = $this->send_request($url); 36 | if (strlen($challenge) != 32) { 37 | $this->failback_process(); 38 | return 0; 39 | } 40 | $this->success_process($challenge); 41 | return 1; 42 | } 43 | 44 | /** 45 | * @param $challenge 46 | */ 47 | private function success_process($challenge) { 48 | $challenge = md5($challenge . $this->private_key); 49 | $result = array( 50 | 'success' => 1, 51 | 'gt' => $this->captcha_id, 52 | 'challenge' => $challenge, 53 | 'new_captcha'=>1 54 | ); 55 | $this->response = $result; 56 | } 57 | 58 | /** 59 | * 60 | */ 61 | private function failback_process() { 62 | $rnd1 = md5(rand(0, 100)); 63 | $rnd2 = md5(rand(0, 100)); 64 | $challenge = $rnd1 . substr($rnd2, 0, 2); 65 | $result = array( 66 | 'success' => 0, 67 | 'gt' => $this->captcha_id, 68 | 'challenge' => $challenge, 69 | 'new_captcha'=>1 70 | ); 71 | $this->response = $result; 72 | } 73 | 74 | /** 75 | * @return mixed 76 | */ 77 | public function get_response_str() { 78 | return json_encode($this->response); 79 | } 80 | 81 | /** 82 | * 返回数组方便扩展 83 | * 84 | * @return mixed 85 | */ 86 | public function get_response() { 87 | return $this->response; 88 | } 89 | 90 | /** 91 | * 正常模式获取验证结果 92 | * 93 | * @param string $challenge 94 | * @param string $validate 95 | * @param string $seccode 96 | * @param array $param 97 | * @return int 98 | */ 99 | public function success_validate($challenge, $validate, $seccode,$param, $json_format=1) { 100 | if (!$this->check_validate($challenge, $validate)) { 101 | return 0; 102 | } 103 | $query = array( 104 | "seccode" => $seccode, 105 | "timestamp"=>time(), 106 | "challenge"=>$challenge, 107 | "captchaid"=>$this->captcha_id, 108 | "json_format"=>$json_format, 109 | "sdk" => self::GT_SDK_VERSION 110 | ); 111 | $query = array_merge($query,$param); 112 | $url = $this->domain . "/validate.php"; 113 | $codevalidate = $this->post_request($url, $query); 114 | $obj = json_decode($codevalidate,true); 115 | if ($obj === false){ 116 | return 0; 117 | } 118 | if ($obj['seccode'] == md5($seccode)) { 119 | return 1; 120 | } else { 121 | return 0; 122 | } 123 | } 124 | 125 | /** 126 | * 宕机模式获取验证结果 127 | * 128 | * @param $challenge 129 | * @param $validate 130 | * @param $seccode 131 | * @return int 132 | */ 133 | public function fail_validate($challenge, $validate, $seccode) { 134 | if(md5($challenge) == $validate){ 135 | return 1; 136 | }else{ 137 | return 0; 138 | } 139 | } 140 | 141 | /** 142 | * @param $challenge 143 | * @param $validate 144 | * @return bool 145 | */ 146 | private function check_validate($challenge, $validate) { 147 | if (strlen($validate) != 32) { 148 | return false; 149 | } 150 | if (md5($this->private_key . 'geetest' . $challenge) != $validate) { 151 | return false; 152 | } 153 | 154 | return true; 155 | } 156 | 157 | /** 158 | * GET 请求 159 | * 160 | * @param $url 161 | * @return mixed|string 162 | */ 163 | private function send_request($url) { 164 | 165 | if (function_exists('curl_exec')) { 166 | $ch = curl_init(); 167 | curl_setopt($ch, CURLOPT_URL, $url); 168 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout); 169 | curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout); 170 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 171 | $data = curl_exec($ch); 172 | $curl_errno = curl_errno($ch); 173 | curl_close($ch); 174 | if ($curl_errno >0) { 175 | return 0; 176 | }else{ 177 | return $data; 178 | } 179 | } else { 180 | $opts = array( 181 | 'http' => array( 182 | 'method' => "GET", 183 | 'timeout' => self::$connectTimeout + self::$socketTimeout, 184 | ) 185 | ); 186 | $context = stream_context_create($opts); 187 | $data = @file_get_contents($url, false, $context); 188 | if($data){ 189 | return $data; 190 | }else{ 191 | return 0; 192 | } 193 | } 194 | } 195 | 196 | /** 197 | * 198 | * @param $url 199 | * @param array $postdata 200 | * @return mixed|string 201 | */ 202 | private function post_request($url, $postdata = '') { 203 | if (!$postdata) { 204 | return false; 205 | } 206 | 207 | $data = http_build_query($postdata); 208 | if (function_exists('curl_exec')) { 209 | $ch = curl_init(); 210 | curl_setopt($ch, CURLOPT_URL, $url); 211 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 212 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout); 213 | curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout); 214 | 215 | //不可能执行到的代码 216 | if (!$postdata) { 217 | curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); 218 | } else { 219 | curl_setopt($ch, CURLOPT_POST, 1); 220 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 221 | } 222 | $data = curl_exec($ch); 223 | 224 | if (curl_errno($ch)) { 225 | $err = sprintf("curl[%s] error[%s]", $url, curl_errno($ch) . ':' . curl_error($ch)); 226 | $this->triggerError($err); 227 | } 228 | 229 | curl_close($ch); 230 | } else { 231 | if ($postdata) { 232 | $opts = array( 233 | 'http' => array( 234 | 'method' => 'POST', 235 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n" . "Content-Length: " . strlen($data) . "\r\n", 236 | 'content' => $data, 237 | 'timeout' => self::$connectTimeout + self::$socketTimeout 238 | ) 239 | ); 240 | $context = stream_context_create($opts); 241 | $data = file_get_contents($url, false, $context); 242 | } 243 | } 244 | 245 | return $data; 246 | } 247 | 248 | 249 | 250 | /** 251 | * @param $err 252 | */ 253 | private function triggerError($err) { 254 | trigger_error($err); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /static/gt.js: -------------------------------------------------------------------------------- 1 | "v0.4.8 Geetest Inc."; 2 | 3 | (function (window) { 4 | "use strict"; 5 | if (typeof window === 'undefined') { 6 | throw new Error('Geetest requires browser environment'); 7 | } 8 | 9 | var document = window.document; 10 | var Math = window.Math; 11 | var head = document.getElementsByTagName("head")[0]; 12 | 13 | function _Object(obj) { 14 | this._obj = obj; 15 | } 16 | 17 | _Object.prototype = { 18 | _each: function (process) { 19 | var _obj = this._obj; 20 | for (var k in _obj) { 21 | if (_obj.hasOwnProperty(k)) { 22 | process(k, _obj[k]); 23 | } 24 | } 25 | return this; 26 | } 27 | }; 28 | 29 | function Config(config) { 30 | var self = this; 31 | new _Object(config)._each(function (key, value) { 32 | self[key] = value; 33 | }); 34 | } 35 | 36 | Config.prototype = { 37 | api_server: 'api.geetest.com', 38 | protocol: 'http://', 39 | typePath: '/gettype.php', 40 | fallback_config: { 41 | slide: { 42 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 43 | type: 'slide', 44 | slide: '/static/js/geetest.0.0.0.js' 45 | }, 46 | fullpage: { 47 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 48 | type: 'fullpage', 49 | fullpage: '/static/js/fullpage.0.0.0.js' 50 | } 51 | }, 52 | _get_fallback_config: function () { 53 | var self = this; 54 | if (isString(self.type)) { 55 | return self.fallback_config[self.type]; 56 | } else if (self.new_captcha) { 57 | return self.fallback_config.fullpage; 58 | } else { 59 | return self.fallback_config.slide; 60 | } 61 | }, 62 | _extend: function (obj) { 63 | var self = this; 64 | new _Object(obj)._each(function (key, value) { 65 | self[key] = value; 66 | }) 67 | } 68 | }; 69 | var isNumber = function (value) { 70 | return (typeof value === 'number'); 71 | }; 72 | var isString = function (value) { 73 | return (typeof value === 'string'); 74 | }; 75 | var isBoolean = function (value) { 76 | return (typeof value === 'boolean'); 77 | }; 78 | var isObject = function (value) { 79 | return (typeof value === 'object' && value !== null); 80 | }; 81 | var isFunction = function (value) { 82 | return (typeof value === 'function'); 83 | }; 84 | var MOBILE = /Mobi/i.test(navigator.userAgent); 85 | var pt = MOBILE ? 3 : 0; 86 | 87 | var callbacks = {}; 88 | var status = {}; 89 | 90 | var nowDate = function () { 91 | var date = new Date(); 92 | var year = date.getFullYear(); 93 | var month = date.getMonth() + 1; 94 | var day = date.getDate(); 95 | var hours = date.getHours(); 96 | var minutes = date.getMinutes(); 97 | var seconds = date.getSeconds(); 98 | 99 | if (month >= 1 && month <= 9) { 100 | month = '0' + month; 101 | } 102 | if (day >= 0 && day <= 9) { 103 | day = '0' + day; 104 | } 105 | if (hours >= 0 && hours <= 9) { 106 | hours = '0' + hours; 107 | } 108 | if (minutes >= 0 && minutes <= 9) { 109 | minutes = '0' + minutes; 110 | } 111 | if (seconds >= 0 && seconds <= 9) { 112 | seconds = '0' + seconds; 113 | } 114 | var currentdate = year + '-' + month + '-' + day + " " + hours + ":" + minutes + ":" + seconds; 115 | return currentdate; 116 | } 117 | 118 | var random = function () { 119 | return parseInt(Math.random() * 10000) + (new Date()).valueOf(); 120 | }; 121 | 122 | var loadScript = function (url, cb) { 123 | var script = document.createElement("script"); 124 | script.charset = "UTF-8"; 125 | script.async = true; 126 | 127 | // 对geetest的静态资源添加 crossOrigin 128 | if ( /static\.geetest\.com/g.test(url)) { 129 | script.crossOrigin = "anonymous"; 130 | } 131 | 132 | script.onerror = function () { 133 | cb(true); 134 | }; 135 | var loaded = false; 136 | script.onload = script.onreadystatechange = function () { 137 | if (!loaded && 138 | (!script.readyState || 139 | "loaded" === script.readyState || 140 | "complete" === script.readyState)) { 141 | 142 | loaded = true; 143 | setTimeout(function () { 144 | cb(false); 145 | }, 0); 146 | } 147 | }; 148 | script.src = url; 149 | head.appendChild(script); 150 | }; 151 | 152 | var normalizeDomain = function (domain) { 153 | // special domain: uems.sysu.edu.cn/jwxt/geetest/ 154 | // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn 155 | return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest 156 | }; 157 | var normalizePath = function (path) { 158 | path = path.replace(/\/+/g, '/'); 159 | if (path.indexOf('/') !== 0) { 160 | path = '/' + path; 161 | } 162 | return path; 163 | }; 164 | var normalizeQuery = function (query) { 165 | if (!query) { 166 | return ''; 167 | } 168 | var q = '?'; 169 | new _Object(query)._each(function (key, value) { 170 | if (isString(value) || isNumber(value) || isBoolean(value)) { 171 | q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; 172 | } 173 | }); 174 | if (q === '?') { 175 | q = ''; 176 | } 177 | return q.replace(/&$/, ''); 178 | }; 179 | var makeURL = function (protocol, domain, path, query) { 180 | domain = normalizeDomain(domain); 181 | 182 | var url = normalizePath(path) + normalizeQuery(query); 183 | if (domain) { 184 | url = protocol + domain + url; 185 | } 186 | 187 | return url; 188 | }; 189 | 190 | var load = function (config, send, protocol, domains, path, query, cb) { 191 | var tryRequest = function (at) { 192 | 193 | var url = makeURL(protocol, domains[at], path, query); 194 | loadScript(url, function (err) { 195 | if (err) { 196 | if (at >= domains.length - 1) { 197 | cb(true); 198 | // report gettype error 199 | if (send) { 200 | config.error_code = 508; 201 | var url = protocol + domains[at] + path; 202 | reportError(config, url); 203 | } 204 | } else { 205 | tryRequest(at + 1); 206 | } 207 | } else { 208 | cb(false); 209 | } 210 | }); 211 | }; 212 | tryRequest(0); 213 | }; 214 | 215 | 216 | var jsonp = function (domains, path, config, callback) { 217 | if (isObject(config.getLib)) { 218 | config._extend(config.getLib); 219 | callback(config); 220 | return; 221 | } 222 | if (config.offline) { 223 | callback(config._get_fallback_config()); 224 | return; 225 | } 226 | 227 | var cb = "geetest_" + random(); 228 | window[cb] = function (data) { 229 | if (data.status == 'success') { 230 | callback(data.data); 231 | } else if (!data.status) { 232 | callback(data); 233 | } else { 234 | callback(config._get_fallback_config()); 235 | } 236 | window[cb] = undefined; 237 | try { 238 | delete window[cb]; 239 | } catch (e) { 240 | } 241 | }; 242 | load(config, true, config.protocol, domains, path, { 243 | gt: config.gt, 244 | callback: cb 245 | }, function (err) { 246 | if (err) { 247 | callback(config._get_fallback_config()); 248 | } 249 | }); 250 | }; 251 | 252 | var reportError = function (config, url) { 253 | load(config, false, config.protocol, ['monitor.geetest.com'], '/monitor/send', { 254 | time: nowDate(), 255 | captcha_id: config.gt, 256 | challenge: config.challenge, 257 | pt: pt, 258 | exception_url: url, 259 | error_code: config.error_code 260 | }, function (err) {}) 261 | } 262 | 263 | var throwError = function (errorType, config) { 264 | var errors = { 265 | networkError: '网络错误', 266 | gtTypeError: 'gt字段不是字符串类型' 267 | }; 268 | if (typeof config.onError === 'function') { 269 | config.onError(errors[errorType]); 270 | } else { 271 | throw new Error(errors[errorType]); 272 | } 273 | }; 274 | 275 | var detect = function () { 276 | return window.Geetest || document.getElementById("gt_lib"); 277 | }; 278 | 279 | if (detect()) { 280 | status.slide = "loaded"; 281 | } 282 | 283 | window.initGeetest = function (userConfig, callback) { 284 | 285 | var config = new Config(userConfig); 286 | 287 | if (userConfig.https) { 288 | config.protocol = 'https://'; 289 | } else if (!userConfig.protocol) { 290 | config.protocol = window.location.protocol + '//'; 291 | } 292 | 293 | // for KFC 294 | if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || 295 | userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { 296 | config.apiserver = 'yumchina.geetest.com/'; // for old js 297 | config.api_server = 'yumchina.geetest.com'; 298 | } 299 | 300 | if(userConfig.gt){ 301 | window.GeeGT = userConfig.gt 302 | } 303 | 304 | if(userConfig.challenge){ 305 | window.GeeChallenge = userConfig.challenge 306 | } 307 | 308 | if (isObject(userConfig.getType)) { 309 | config._extend(userConfig.getType); 310 | } 311 | jsonp([config.api_server || config.apiserver], config.typePath, config, function (newConfig) { 312 | var type = newConfig.type; 313 | var init = function () { 314 | config._extend(newConfig); 315 | callback(new window.Geetest(config)); 316 | }; 317 | 318 | callbacks[type] = callbacks[type] || []; 319 | var s = status[type] || 'init'; 320 | if (s === 'init') { 321 | status[type] = 'loading'; 322 | 323 | callbacks[type].push(init); 324 | 325 | load(config, true, config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { 326 | if (err) { 327 | status[type] = 'fail'; 328 | throwError('networkError', config); 329 | } else { 330 | status[type] = 'loaded'; 331 | var cbs = callbacks[type]; 332 | for (var i = 0, len = cbs.length; i < len; i = i + 1) { 333 | var cb = cbs[i]; 334 | if (isFunction(cb)) { 335 | cb(); 336 | } 337 | } 338 | callbacks[type] = []; 339 | } 340 | }); 341 | } else if (s === "loaded") { 342 | init(); 343 | } else if (s === "fail") { 344 | throwError('networkError', config); 345 | } else if (s === "loading") { 346 | callbacks[type].push(init); 347 | } 348 | }); 349 | 350 | }; 351 | 352 | 353 | })(window); 354 | 355 | --------------------------------------------------------------------------------