├── .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 | ![geetesst](https://cloud.githubusercontent.com/assets/6011686/12508320/385a56a6-c136-11e5-9353-b686c85bd37a.gif) 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 |
89 | {{ csrf_field() }} 90 |
91 | 92 | 93 |
94 |
95 | 96 | 97 |
98 |
99 |
100 | 101 | 124 |
125 |
126 | 127 |
128 |
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 | --------------------------------------------------------------------------------