├── 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 |
--------------------------------------------------------------------------------