├── .gitignore
├── README.md
├── composer.json
└── src
├── GeeCaptcha.php
└── GeetestLib.php
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Laravel template
2 | /bootstrap/compiled.php
3 | .env.*.php
4 | .env.php
5 | .env
6 | vendor
7 | .idea
8 | # Created by .ignore support plugin (hsz.mobi)
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 极验证 Composer Package
2 |
3 | ## 视频教程: [Laravist](https://laravist.com/series/tools-that-are-dame-good-for-developer/episodes/1)
4 |
5 | >说明:由于geetest本身的composer package有很多不必要的文件,这里是最精简的版本,只用于验证码验证。
6 |
7 | ## 演示
8 |
9 | 
10 |
11 | ## Usage
12 |
13 | 安装 (目前的版本是 1.0):
14 |
15 | ```
16 | composer require laravist/geecaptcha
17 | ```
18 |
19 | 1. 实例化
20 | ```php
21 | $captcha = new \Laravist\GeeCaptcha\GeeCaptcha($captcha_id, $private_key);
22 | ```
23 |
24 | 2. 使用的使用可以这样判断验证码是否验证成功(通常是post路由里):
25 |
26 | ```php
27 | if ($captcha->isFromGTServer() && $captcha->success())
28 | {
29 | // 登录的代码逻辑在这里
30 | }
31 |
32 | ```
33 | > 注意: 上面第一个判断是检测GT(geetest.com)的服务器是否正常,第二个才是检测验证码是否正确。
34 |
35 | 3. 对于需要重新生成验证码的时候(通常放在get方式的路由里):
36 |
37 | ```php
38 | $captcha = new \Laravist\GeeCaptcha\GeeCaptcha($captcha_id, $private_key);
39 | echo $captcha->GTServerIsNormal();
40 | ```
41 |
42 | ## Laravel 使用用例
43 |
44 | routes
45 |
46 | ```php
47 | Route::group(['middleware' => ['web']], function () {
48 | Route::get('/login', function () {
49 | return view('login');
50 | });
51 |
52 | Route::post('/verify', function () {
53 | $captcha = new \Laravist\GeeCaptcha\GeeCaptcha(env('CAPTCHA_ID'), env('PRIVATE_KEY'));
54 | if ($captcha->isFromGTServer()) {
55 | if($captcha->success()){
56 | return 'success';
57 | }
58 | return 'no';
59 | }
60 | if ($captcha->hasAnswer()) {
61 | return "answer";
62 | }
63 | return "no answer";
64 | });
65 |
66 | Route::get('/captcha', function () {
67 | $captcha = new \Laravist\GeeCaptcha\GeeCaptcha(env('CAPTCHA_ID'), env('PRIVATE_KEY'));
68 |
69 | echo $captcha->GTServerIsNormal();
70 | });
71 |
72 | });
73 | ```
74 | login视图:
75 |
76 | ```html
77 |
78 |
79 |
80 | Laravel Geetest
81 |
82 |
83 |
84 |
85 |
86 |
87 |
Laravel 5
88 |
129 |
130 |
131 |
132 |
133 |
134 | ```
135 |
136 |
137 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravist/geecaptcha",
3 | "description": "A geetest captcha package by laravist",
4 | "type": "php",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "JellyBool",
9 | "email": "jellybool@outlook.com"
10 | }
11 | ],
12 | "autoload": {
13 | "psr-4": {
14 | "Laravist\\GeeCaptcha\\": "src/"
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/GeeCaptcha.php:
--------------------------------------------------------------------------------
1 | success_validate($_POST['geetest_challenge'], $_POST['geetest_validate'], $_POST['geetest_seccode']);
37 |
38 | return $result;
39 | }
40 |
41 | /**
42 | * @return int
43 | *
44 | * GT 服务器是否有回应
45 | */
46 | public function hasAnswer()
47 | {
48 | return $this->fail_validate($_POST['geetest_challenge'], $_POST['geetest_validate']);
49 | }
50 |
51 | /**
52 | * @return mixed
53 | *
54 | * 判断GT 服务器是否正常
55 | */
56 | public function GTServerIsNormal()
57 | {
58 | $status = $this->pre_process();
59 | $_SESSION['gtserver'] = $status;
60 |
61 | return $this->get_response_str();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/GeetestLib.php:
--------------------------------------------------------------------------------
1 | captcha_id = $captcha_id;
23 | $this->private_key = $private_key;
24 | }
25 |
26 | /**
27 | * 判断极验服务器是否down机
28 | *
29 | * @param null $user_id
30 | * @return int
31 | */
32 | public function pre_process($user_id = null) {
33 | $url = "http://api.geetest.com/register.php?gt=" . $this->captcha_id;
34 | if (($user_id != null) and (is_string($user_id))) {
35 | $url = $url . "&user_id=" . $user_id;
36 | }
37 | $challenge = $this->send_request($url);
38 |
39 | if (strlen($challenge) != 32) {
40 | $this->failback_process();
41 |
42 | return 0;
43 | }
44 | $this->success_process($challenge);
45 |
46 | return 1;
47 | }
48 |
49 | /**
50 | * @param $challenge
51 | */
52 | private function success_process($challenge) {
53 | $challenge = md5($challenge . $this->private_key);
54 | $result = array(
55 | 'success' => 1,
56 | 'gt' => $this->captcha_id,
57 | 'challenge' => $challenge
58 | );
59 | $this->response = $result;
60 | }
61 |
62 | /**
63 | *
64 | */
65 | private function failback_process() {
66 | $rnd1 = md5(rand(0, 100));
67 | $rnd2 = md5(rand(0, 100));
68 | $challenge = $rnd1 . substr($rnd2, 0, 2);
69 | $result = array(
70 | 'success' => 0,
71 | 'gt' => $this->captcha_id,
72 | 'challenge' => $challenge
73 | );
74 | $this->response = $result;
75 | }
76 |
77 | /**
78 | * @return mixed
79 | */
80 | public function get_response_str() {
81 | return json_encode($this->response);
82 | }
83 |
84 | /**
85 | * 返回数组方便扩展
86 | *
87 | * @return mixed
88 | */
89 | public function get_response() {
90 | return $this->response;
91 | }
92 |
93 | /**
94 | * 正常模式获取验证结果
95 | *
96 | * @param $challenge
97 | * @param $validate
98 | * @param $seccode
99 | * @param null $user_id
100 | * @return int
101 | */
102 | public function success_validate($challenge, $validate, $seccode, $user_id = null) {
103 | if (!$this->check_validate($challenge, $validate)) {
104 | return 0;
105 | }
106 | $data = array(
107 | "seccode" => $seccode,
108 | "sdk" => self::GT_SDK_VERSION,
109 | );
110 | if (($user_id != null) and (is_string($user_id))) {
111 | $data["user_id"] = $user_id;
112 | }
113 | $url = "http://api.geetest.com/validate.php";
114 | $codevalidate = $this->post_request($url, $data);
115 | if ($codevalidate == md5($seccode)) {
116 | return 1;
117 | } else {
118 | if ($codevalidate == "false") {
119 | return 0;
120 | } else {
121 | return 0;
122 | }
123 | }
124 | }
125 |
126 | /**
127 | * 宕机模式获取验证结果
128 | *
129 | * @param $challenge
130 | * @param $validate
131 | * @param $seccode
132 | * @return int
133 | */
134 | public function fail_validate($challenge, $validate, $seccode) {
135 | if ($validate) {
136 | $value = explode("_", $validate);
137 | $ans = $this->decode_response($challenge, $value['0']);
138 | $bg_idx = $this->decode_response($challenge, $value['1']);
139 | $grp_idx = $this->decode_response($challenge, $value['2']);
140 | $x_pos = $this->get_failback_pic_ans($bg_idx, $grp_idx);
141 | $answer = abs($ans - $x_pos);
142 | if ($answer < 4) {
143 | return 1;
144 | } else {
145 | return 0;
146 | }
147 | } else {
148 | return 0;
149 | }
150 |
151 | }
152 |
153 | /**
154 | * @param $challenge
155 | * @param $validate
156 | * @return bool
157 | */
158 | private function check_validate($challenge, $validate) {
159 | if (strlen($validate) != 32) {
160 | return false;
161 | }
162 | if (md5($this->private_key . 'geetest' . $challenge) != $validate) {
163 | return false;
164 | }
165 |
166 | return true;
167 | }
168 |
169 | /**
170 | * GET 请求
171 | *
172 | * @param $url
173 | * @return mixed|string
174 | */
175 | private function send_request($url) {
176 |
177 | if (function_exists('curl_exec')) {
178 |
179 | $ch = curl_init();
180 | curl_setopt($ch, CURLOPT_URL, $url);
181 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout);
182 | curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout);
183 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
184 |
185 | $data = curl_exec($ch);
186 |
187 | if (curl_errno($ch)) {
188 | $err = sprintf("curl[%s] error[%s]", $url, curl_errno($ch) . ':' . curl_error($ch));
189 | $this->triggerError($err);
190 | }
191 |
192 | curl_close($ch);
193 | } else {
194 | $opts = array(
195 | 'http' => array(
196 | 'method' => "GET",
197 | 'timeout' => self::$connectTimeout + self::$socketTimeout,
198 | )
199 | );
200 | $context = stream_context_create($opts);
201 | $data = file_get_contents($url, false, $context);
202 | }
203 |
204 | return $data;
205 | }
206 |
207 | /**
208 | *
209 | * @param $url
210 | * @param array $postdata
211 | * @return mixed|string
212 | */
213 | private function post_request($url, $postdata = '') {
214 | if (!$postdata) {
215 | return false;
216 | }
217 |
218 | $data = http_build_query($postdata);
219 | if (function_exists('curl_exec')) {
220 | $ch = curl_init();
221 | curl_setopt($ch, CURLOPT_URL, $url);
222 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
223 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout);
224 | curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout);
225 |
226 | //不可能执行到的代码
227 | if (!$postdata) {
228 | curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
229 | } else {
230 | curl_setopt($ch, CURLOPT_POST, 1);
231 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
232 | }
233 | $data = curl_exec($ch);
234 |
235 | if (curl_errno($ch)) {
236 | $err = sprintf("curl[%s] error[%s]", $url, curl_errno($ch) . ':' . curl_error($ch));
237 | $this->triggerError($err);
238 | }
239 |
240 | curl_close($ch);
241 | } else {
242 | if ($postdata) {
243 | $opts = array(
244 | 'http' => array(
245 | 'method' => 'POST',
246 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n" . "Content-Length: " . strlen($data) . "\r\n",
247 | 'content' => $data,
248 | 'timeout' => self::$connectTimeout + self::$socketTimeout
249 | )
250 | );
251 | $context = stream_context_create($opts);
252 | $data = file_get_contents($url, false, $context);
253 | }
254 | }
255 |
256 | return $data;
257 | }
258 |
259 |
260 | /**
261 | * 解码随机参数
262 | *
263 | * @param $challenge
264 | * @param $string
265 | * @return int
266 | */
267 | private function decode_response($challenge, $string) {
268 | if (strlen($string) > 100) {
269 | return 0;
270 | }
271 | $key = array();
272 | $chongfu = array();
273 | $shuzi = array("0" => 1, "1" => 2, "2" => 5, "3" => 10, "4" => 50);
274 | $count = 0;
275 | $res = 0;
276 | $array_challenge = str_split($challenge);
277 | $array_value = str_split($string);
278 | for ($i = 0; $i < strlen($challenge); $i++) {
279 | $item = $array_challenge[$i];
280 | if (in_array($item, $chongfu)) {
281 | continue;
282 | } else {
283 | $value = $shuzi[$count % 5];
284 | array_push($chongfu, $item);
285 | $count++;
286 | $key[$item] = $value;
287 | }
288 | }
289 |
290 | for ($j = 0; $j < strlen($string); $j++) {
291 | $res += $key[$array_value[$j]];
292 | }
293 | $res = $res - $this->decodeRandBase($challenge);
294 |
295 | return $res;
296 | }
297 |
298 |
299 | /**
300 | * @param $x_str
301 | * @return int
302 | */
303 | private function get_x_pos_from_str($x_str) {
304 | if (strlen($x_str) != 5) {
305 | return 0;
306 | }
307 | $sum_val = 0;
308 | $x_pos_sup = 200;
309 | $sum_val = base_convert($x_str, 16, 10);
310 | $result = $sum_val % $x_pos_sup;
311 | $result = ($result < 40) ? 40 : $result;
312 |
313 | return $result;
314 | }
315 |
316 | /**
317 | * @param $full_bg_index
318 | * @param $img_grp_index
319 | * @return int
320 | */
321 | private function get_failback_pic_ans($full_bg_index, $img_grp_index) {
322 | $full_bg_name = substr(md5($full_bg_index), 0, 9);
323 | $bg_name = substr(md5($img_grp_index), 10, 9);
324 |
325 | $answer_decode = "";
326 | // 通过两个字符串奇数和偶数位拼接产生答案位
327 | for ($i = 0; $i < 9; $i++) {
328 | if ($i % 2 == 0) {
329 | $answer_decode = $answer_decode . $full_bg_name[$i];
330 | } elseif ($i % 2 == 1) {
331 | $answer_decode = $answer_decode . $bg_name[$i];
332 | }
333 | }
334 | $x_decode = substr($answer_decode, 4, 5);
335 | $x_pos = $this->get_x_pos_from_str($x_decode);
336 |
337 | return $x_pos;
338 | }
339 |
340 | /**
341 | * 输入的两位的随机数字,解码出偏移量
342 | *
343 | * @param $challenge
344 | * @return mixed
345 | */
346 | private function decodeRandBase($challenge) {
347 | $base = substr($challenge, 32, 2);
348 | $tempArray = array();
349 | for ($i = 0; $i < strlen($base); $i++) {
350 | $tempAscii = ord($base[$i]);
351 | $result = ($tempAscii > 57) ? ($tempAscii - 87) : ($tempAscii - 48);
352 | array_push($tempArray, $result);
353 | }
354 | $decodeRes = $tempArray['0'] * 36 + $tempArray['1'];
355 |
356 | return $decodeRes;
357 | }
358 |
359 | /**
360 | * @param $err
361 | */
362 | private function triggerError($err) {
363 | trigger_error($err);
364 | }
365 | }
366 |
--------------------------------------------------------------------------------