├── UPGRADE.md ├── .gitignore ├── resources ├── views │ ├── login_button.blade.php │ ├── login_remember.blade.php │ ├── hcaptcha │ │ ├── login_style.blade.php │ │ └── login.blade.php │ ├── vaptcha │ │ ├── login.blade.php │ │ └── login_style.blade.php │ ├── recaptcha │ │ └── login.blade.php │ ├── recaptchav2 │ │ ├── login_style.blade.php │ │ └── login.blade.php │ ├── dingxiang │ │ ├── login_style.blade.php │ │ └── login.blade.php │ ├── tencent │ │ └── login.blade.php │ ├── geetest │ │ ├── login.blade.php │ │ └── login_style.blade.php │ ├── wangyi │ │ ├── login.blade.php │ │ └── login_style.blade.php │ ├── verify5 │ │ └── login_style.blade.php │ ├── yunpian │ │ └── login_style.blade.php │ └── login_base.blade.php ├── lang │ ├── zh-CN │ │ └── dcat-auth-captcha.php │ ├── zh_CN │ │ └── dcat-auth-captcha.php │ └── en │ │ └── dcat-auth-captcha.php └── assets │ └── js │ └── geetest │ └── gt.js ├── CHANGE_LOG.md ├── version.php ├── src ├── Http │ ├── routes.php │ ├── Middleware │ │ ├── DcatAuthCaptchaThrottleMiddlewareBelow8.php │ │ └── DcatAuthCaptchaThrottleMiddleware.php │ └── Controllers │ │ └── DcatAuthCaptchaController.php ├── DcatAuthCaptchaServiceProvider.php └── Setting.php ├── LICENSE ├── composer.json └── README.md /UPGRADE.md: -------------------------------------------------------------------------------- 1 | **需要改动的升级将会在这里详细说明** -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | phpunit.phar 3 | /vendor 4 | composer.phar 5 | composer.lock 6 | *.project 7 | .idea/ -------------------------------------------------------------------------------- /resources/views/login_button.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGE_LOG.md: -------------------------------------------------------------------------------- 1 | ### v2.0.2(2021-08-20) 2 | 3 | - 添加验证时请求超时配置(可选配置) 4 | - 添加尝试登录限流功能(兼容Laravel8和Laravel8以下的限流中间件) 5 | 6 | ### v2.0.1(2021-05-07) 7 | 8 | - 兼容guzzlehttp/guzzle 7.*版本 9 | 10 | ### v2.0.0(2021-04-07) 11 | 12 | - 第一版,移植[asundust/auth-captcha](https://github.com/asundust/auth-captcha) ,支持部分验证码 -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | [ 5 | '移植[https://github.com/asundust/auth-captcha](https://github.com/asundust/auth-captcha)第一版', 6 | '大版本号跟随dcat-admin版本号走', 7 | ], 8 | '2.0.1' => [ 9 | '兼容guzzlehttp/guzzle 7.*版本', 10 | ], 11 | '2.0.2' => [ 12 | '添加验证时请求超时配置(可选配置)', 13 | '添加尝试登录限流功能(兼容Laravel8和Laravel8以下的限流中间件)', 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /src/Http/routes.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | @if(config('admin.auth.remember')) 4 |
5 |
6 | 8 | 9 | 10 | 11 | 12 | 13 | {{ trans('admin.remember_me') }} 14 |
15 |
16 | @endif 17 |
18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 asundust 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asundust/dcat-auth-captcha", 3 | "alias": "登录滑动验证码", 4 | "description": "Sliding captcha for Dcat-Admin auth, Multiple platform support / Dcat-Admin登录 (滑动)验证插件 多平台支持", 5 | "type": "library", 6 | "keywords": [ 7 | "dcat-admin", 8 | "extension", 9 | "sliding captcha", 10 | "auth", 11 | "login", 12 | "captcha", 13 | "auth captcha", 14 | "multiple platform" 15 | ], 16 | "homepage": "https://github.com/asundust/dcat-auth-captcha", 17 | "license": "MIT", 18 | "authors": [ 19 | { 20 | "name": "asundust", 21 | "email": "asundust@qq.com" 22 | } 23 | ], 24 | "require": { 25 | "php": ">=7.1.0", 26 | "ext-json": "*", 27 | "dcat/laravel-admin": "~2.0", 28 | "guzzlehttp/guzzle": "^6.3 || ^7.3", 29 | "jenssegers/agent": "^2.6", 30 | "mnabialek/laravel-version": "^1.0" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Asundust\\DcatAuthCaptcha\\": "src/" 35 | } 36 | }, 37 | "extra": { 38 | "dcat-admin": "Asundust\\DcatAuthCaptcha\\DcatAuthCaptchaServiceProvider", 39 | "laravel": { 40 | "providers": [ 41 | "Asundust\\DcatAuthCaptcha\\DcatAuthCaptchaServiceProvider" 42 | ] 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /resources/lang/zh-CN/dcat-auth-captcha.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'dingxiang' => '顶象', 6 | 'geetest' => '极验', 7 | 'hcaptcha' => 'hCaptcha', 8 | 'recaptchav2' => 'Recaptcha v2(谷歌)', 9 | 'recaptcha' => 'Recaptcha v3(谷歌)', 10 | 'tencent' => '腾讯防水墙', 11 | 'verify5' => 'V5', 12 | 'vaptcha' => 'Vaptcha', 13 | 'wangyi' => '网易易盾', 14 | 'yunpian' => '云片', 15 | ], 16 | 'messages' => [ 17 | 'fail' => '滑动验证未通过,请重试。', 18 | 'config' => '请完成验证。', 19 | 'error' => '配置错误。', 20 | 'complete' => '请完成验证。', 21 | 'login_try_throttle_error' => '请稍后再试。', 22 | ], 23 | 'config' => '验证码配置', 24 | 'provider' => '验证码服务商', 25 | 'style' => '验证码样式', 26 | 'appid' => 'AppId', 27 | 'secret' => 'Secret', 28 | 'timeout' => '请求超时', 29 | 'secret_key' => 'Secret Key', 30 | 'domain' => '服务域名', 31 | 'score' => '可信任分数', 32 | 'host' => '主机', 33 | 'ext_config' => '额外配置', 34 | 'login_try_throttle' => '尝试登录限流', 35 | 'tip_json' => '目前仅支持填写JSON,参见官方文档。', 36 | 'timeout_help' => '后端验证超时时间,默认为5秒', 37 | 'parameters_document' => '参数说明,参见文档。', 38 | 'document_address' => '文档地址', 39 | ]; 40 | -------------------------------------------------------------------------------- /resources/lang/zh_CN/dcat-auth-captcha.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'dingxiang' => '顶象', 6 | 'geetest' => '极验', 7 | 'hcaptcha' => 'hCaptcha', 8 | 'recaptchav2' => 'Recaptcha v2(谷歌)', 9 | 'recaptcha' => 'Recaptcha v3(谷歌)', 10 | 'tencent' => '腾讯防水墙', 11 | 'verify5' => 'V5', 12 | 'vaptcha' => 'Vaptcha', 13 | 'wangyi' => '网易易盾', 14 | 'yunpian' => '云片', 15 | ], 16 | 'messages' => [ 17 | 'fail' => '滑动验证未通过,请重试。', 18 | 'config' => '请完成验证。', 19 | 'error' => '配置错误。', 20 | 'complete' => '请完成验证。', 21 | 'login_try_throttle_error' => '请稍后再试。', 22 | ], 23 | 'config' => '验证码配置', 24 | 'provider' => '验证码服务商', 25 | 'style' => '验证码样式', 26 | 'appid' => 'AppId', 27 | 'secret' => 'Secret', 28 | 'timeout' => '请求超时', 29 | 'secret_key' => 'Secret Key', 30 | 'domain' => '服务域名', 31 | 'score' => '可信任分数', 32 | 'host' => '主机', 33 | 'ext_config' => '额外配置', 34 | 'login_try_throttle' => '尝试登录限流', 35 | 'tip_json' => '目前仅支持填写JSON,参见官方文档。', 36 | 'timeout_help' => '后端验证超时时间,默认为5秒', 37 | 'parameters_document' => '参数说明,参见文档。', 38 | 'document_address' => '文档地址', 39 | ]; 40 | -------------------------------------------------------------------------------- /resources/views/hcaptcha/login_style.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
4 |
6 |
7 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 8 | 9 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 10 | @endsection 11 | @section('js') 12 | 13 | 35 | @endsection -------------------------------------------------------------------------------- /resources/views/vaptcha/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
4 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 5 | 6 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 7 | @endsection 8 | @section('js') 9 | 10 | 36 | @endsection -------------------------------------------------------------------------------- /resources/views/hcaptcha/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 4 | 5 | 11 | @endsection 12 | @section('js') 13 | 14 | 40 | @endsection -------------------------------------------------------------------------------- /resources/views/recaptcha/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 4 | 5 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 6 | @endsection 7 | @section('js') 8 | 10 | 35 | @endsection -------------------------------------------------------------------------------- /resources/views/recaptchav2/login_style.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
4 |
7 |
8 |
9 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 10 | 11 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 12 | @endsection 13 | @section('js') 14 | 16 | 36 | @endsection -------------------------------------------------------------------------------- /resources/lang/en/dcat-auth-captcha.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'dingxiang' => 'Ding Xiang', 6 | 'geetest' => 'Geetest', 7 | 'hcaptcha' => 'hCaptcha', 8 | 'recaptchav2' => 'Google Recaptcha v2', 9 | 'recaptcha' => 'Google Recaptcha v3', 10 | 'tencent' => 'Tencent Waterproof Wall', 11 | 'verify5' => 'Verify5', 12 | 'vaptcha' => 'Vaptcha', 13 | 'wangyi' => 'NetEase Dun', 14 | 'yunpian' => 'Yunpian', 15 | ], 16 | 'messages' => [ 17 | 'fail' => 'Sliding validation failed. Please try again.', 18 | 'config' => 'Please complete the validation.', 19 | 'error' => 'Config Error.', 20 | 'complete' => 'Please complete the validation.', 21 | 'login_try_throttle_error' => 'Please try again later.', 22 | ], 23 | 'config' => 'Captcha Config', 24 | 'provider' => 'Captcha provider', 25 | 'style' => 'Captcha style', 26 | 'appid' => 'AppId', 27 | 'secret' => 'Secret', 28 | 'timeout' => 'Request Timeout', 29 | 'secret_key' => 'Secret Key', 30 | 'domain' => 'Domain', 31 | 'score' => 'Score', 32 | 'host' => 'Host', 33 | 'ext_config' => 'Ext', 34 | 'login_try_throttle' => 'Login try throttle', 35 | 'tip_json' => 'At present, only JSON is supported. Please refer to the official documents.', 36 | 'timeout_help' => 'Verification timeout, default is 5 seconds', 37 | 'parameters_document' => 'For details about the parameters,see the document.', 38 | 'document_address' => 'Document Address', 39 | ]; 40 | -------------------------------------------------------------------------------- /resources/views/recaptchav2/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 4 | 5 | 11 | @endsection 12 | @section('js') 13 | 15 | 42 | @endsection -------------------------------------------------------------------------------- /resources/views/dingxiang/login_style.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
4 |
5 |
6 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 7 | 8 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 9 | @endsection 10 | @section('js') 11 | 12 | 41 | @endsection -------------------------------------------------------------------------------- /resources/views/dingxiang/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
4 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 5 | 6 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 7 | @endsection 8 | @section('js') 9 | 10 | 43 | @endsection -------------------------------------------------------------------------------- /resources/views/tencent/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 4 | 5 | 6 |
7 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 8 | @endsection 9 | @section('js') 10 | 11 | 42 | @endsection -------------------------------------------------------------------------------- /resources/views/geetest/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 4 | 5 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 6 | @endsection 7 | @section('js') 8 | 46 | @endsection -------------------------------------------------------------------------------- /resources/views/wangyi/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
5 | @if($errors->has('captcha')) 6 | @foreach($errors->get('captcha') as $message) 7 |
9 | @endforeach 10 | @endif 11 |
12 |
13 | 14 | @endsection 15 | @section('js') 16 | 17 | 56 | @endsection -------------------------------------------------------------------------------- /resources/views/wangyi/login_style.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('css') 3 | 8 | @endsection 9 | @section('content') 10 |
12 | @if($errors->has('captcha')) 13 | @foreach($errors->get('captcha') as $message) 14 |
16 | @endforeach 17 | @endif 18 |
19 |
20 |
21 |
22 | 23 | @endsection 24 | @section('js') 25 | 26 | 60 | @endsection -------------------------------------------------------------------------------- /resources/views/verify5/login_style.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
4 |
6 |
7 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 8 | 9 | 13 | @endsection 14 | @section('js') 15 | 16 | 48 | @endsection -------------------------------------------------------------------------------- /resources/views/geetest/login_style.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
4 |
5 |
6 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 7 | 8 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 9 | @endsection 10 | @section('js') 11 | 47 | @endsection -------------------------------------------------------------------------------- /resources/views/yunpian/login_style.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('content') 3 |
4 |
5 |
6 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_remember') 7 | 8 | 9 | @include(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_button') 10 | @endsection 11 | @section('js') 12 | 13 | 49 | @endsection -------------------------------------------------------------------------------- /resources/views/vaptcha/login_style.blade.php: -------------------------------------------------------------------------------- 1 | @extends(Asundust\DcatAuthCaptcha\DcatAuthCaptchaServiceProvider::instance()->getName().'::login_base') 2 | @section('css') 3 | 40 | @endsection 41 | @section('content') 42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | VAPTCHA启动中... 50 |
51 |
52 |
53 |
54 | @endsection 55 | @section('js') 56 | 57 | 82 | @endsection -------------------------------------------------------------------------------- /src/Http/Middleware/DcatAuthCaptchaThrottleMiddlewareBelow8.php: -------------------------------------------------------------------------------- 1 | resolveRequestSignature($request); 29 | 30 | $maxAttempts = $this->resolveMaxAttempts($request, $maxAttempts); 31 | 32 | if ($this->limiter->tooManyAttempts($key, $maxAttempts)) { 33 | return $this->buildException($key, $maxAttempts); 34 | } 35 | 36 | $this->limiter->hit($key, $decayMinutes * 60); 37 | 38 | $response = $next($request); 39 | 40 | return $this->addHeaders( 41 | $response, 42 | $maxAttempts, 43 | $this->calculateRemainingAttempts($key, $maxAttempts) 44 | ); 45 | } 46 | 47 | /** 48 | * Create a 'too many attempts' exception. 49 | * 50 | * @param string $key 51 | * @param int $maxAttempts 52 | * 53 | * @return \Illuminate\Http\Exceptions\ThrottleRequestsException|\Illuminate\Http\JsonResponse 54 | */ 55 | protected function buildException($key, $maxAttempts) 56 | { 57 | $retryAfter = $this->getTimeUntilNextRetry($key); 58 | 59 | $headers = $this->getHeaders( 60 | $maxAttempts, 61 | $this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter), 62 | $retryAfter 63 | ); 64 | 65 | return $this->captchaErrorResponse($this->getMessage()); 66 | } 67 | 68 | /** 69 | * @return array|string|null 70 | */ 71 | private function getMessage() 72 | { 73 | return DcatAuthCaptchaServiceProvider::trans('dcat-auth-captcha.messages.login_try_throttle_error'); 74 | } 75 | 76 | /** 77 | * CaptchaErrorResponse. 78 | * 79 | * @param $message 80 | * 81 | * @return \Illuminate\Http\JsonResponse 82 | */ 83 | public function captchaErrorResponse($message) 84 | { 85 | return $this->response() 86 | ->error($message) 87 | ->send(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Http/Middleware/DcatAuthCaptchaThrottleMiddleware.php: -------------------------------------------------------------------------------- 1 | limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) { 27 | return $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback); 28 | } 29 | 30 | $this->limiter->hit($limit->key, $limit->decayMinutes * 60); 31 | } 32 | 33 | $response = $next($request); 34 | 35 | foreach ($limits as $limit) { 36 | $response = $this->addHeaders( 37 | $response, 38 | $limit->maxAttempts, 39 | $this->calculateRemainingAttempts($limit->key, $limit->maxAttempts) 40 | ); 41 | } 42 | 43 | return $response; 44 | } 45 | 46 | /** 47 | * Create a 'too many attempts' exception. 48 | * 49 | * @param \Illuminate\Http\Request $request 50 | * @param string $key 51 | * @param int $maxAttempts 52 | * @param callable|null $responseCallback 53 | * 54 | * @return \Illuminate\Http\Exceptions\HttpResponseException|\Illuminate\Http\Exceptions\ThrottleRequestsException|\Illuminate\Http\JsonResponse 55 | */ 56 | protected function buildException($request, $key, $maxAttempts, $responseCallback = null) 57 | { 58 | $retryAfter = $this->getTimeUntilNextRetry($key); 59 | 60 | $headers = $this->getHeaders( 61 | $maxAttempts, 62 | $this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter), 63 | $retryAfter 64 | ); 65 | 66 | return $this->captchaErrorResponse($this->getMessage()); 67 | } 68 | 69 | /** 70 | * @return array|string|null 71 | */ 72 | private function getMessage() 73 | { 74 | return DcatAuthCaptchaServiceProvider::trans('dcat-auth-captcha.messages.login_try_throttle_error'); 75 | } 76 | 77 | /** 78 | * CaptchaErrorResponse. 79 | * 80 | * @param $message 81 | * 82 | * @return \Illuminate\Http\JsonResponse 83 | */ 84 | public function captchaErrorResponse($message) 85 | { 86 | return $this->response() 87 | ->error($message) 88 | ->send(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Setting.php: -------------------------------------------------------------------------------- 1 | toTrans('config'); 12 | } 13 | 14 | public function form() 15 | { 16 | $providers = [ 17 | 'dingxiang' => $this->toTrans('providers.dingxiang'), 18 | 'geetest' => $this->toTrans('providers.geetest'), 19 | 'hcaptcha' => $this->toTrans('providers.hcaptcha'), 20 | 'recaptchav2' => $this->toTrans('providers.recaptchav2'), 21 | 'recaptcha' => $this->toTrans('providers.recaptcha'), 22 | 'tencent' => $this->toTrans('providers.tencent'), 23 | 'verify5' => $this->toTrans('providers.verify5'), 24 | // 'vaptcha' => $this->toTrans('providers.vaptcha'), 25 | // 'wangyi' => $this->toTrans('providers.wangyi'), 26 | // 'yunpian' => $this->toTrans('providers.yunpian'), 27 | ]; 28 | 29 | $documentHelp = $this->toTrans('parameters_document'); 30 | $documentAddress = ' ' . $this->toTrans('document_address') . ''; 31 | 32 | $this->select('provider', $this->toTrans('provider')) 33 | ->options($providers) 34 | ->required(); 35 | $this->text('appid', $this->toTrans('appid'))->required(); 36 | $this->text('secret', $this->toTrans('secret'))->required(); 37 | $this->text('style', $this->toTrans('style')) 38 | ->help($documentHelp . $documentAddress); 39 | $this->text('timeout', $this->toTrans('timeout')) 40 | ->help($this->toTrans('timeout_help')); 41 | $this->text('secret_key', $this->toTrans('secret_key')) 42 | ->help($documentHelp . $documentAddress); 43 | $this->text('host', $this->toTrans('host')) 44 | ->help($documentHelp . $documentAddress); 45 | $this->text('domain', $this->toTrans('domain')) 46 | ->help($documentHelp . $documentAddress); 47 | $this->text('score', $this->toTrans('score')) 48 | ->help($documentHelp . $documentAddress); 49 | $this->textarea('ext_config', $this->toTrans('ext_config')) 50 | ->help($this->toTrans('tip_json')); 51 | if (in_array(config('app.locale'), ['zh-CN', 'zh_CN'])) { 52 | $throttleHref = 'https://learnku.com/docs/laravel/7.x/routing/7458#5c3711'; 53 | } else { 54 | $throttleHref = 'https://laravel.com/docs/7.x/routing#rate-limiting'; 55 | } 56 | $throttleDocumentAddress = ' ' . $this->toTrans('document_address') . ''; 57 | $this->text('login_try_throttle', $this->toTrans('login_try_throttle')) 58 | ->help($documentHelp . $throttleDocumentAddress); 59 | } 60 | 61 | /** 62 | * @param $key 63 | */ 64 | public function toTrans($key): string 65 | { 66 | return $this->trans('dcat-auth-captcha.' . $key); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /resources/views/login_base.blade.php: -------------------------------------------------------------------------------- 1 | 28 | 29 |
30 |
31 | 34 |
35 | 103 |
104 |
105 |
106 | 107 | 119 | @yield('js') -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Dcat-Admin登录 滑动验证插件 多平台支持 2 | ====== 3 | 4 | ![StyleCI build status](https://github.styleci.io/repos/355441406/shield) 5 | 6 | Dcat-Admin登录 滑动验证插件 多平台支持 7 | 8 | > 另有 [Laravel-Admin版](https://github.com/asundust/auth-captcha) 9 | 10 | ### Demo演示 11 | 12 | [~~演示站点~~](https://captcha.leeay.com)(暂时无,目前地址为Laravel-Admin版的演示地址) 13 | 14 | ### 支持(按照字母顺序) 15 | 16 | - [顶象](https://www.dingxiang-inc.com/business/captcha):heavy_check_mark: 17 | - [极验](http://www.geetest.com):heavy_check_mark: 18 | - [hCaptcha(和谷歌Recaptcha v2一样)](https://www.hcaptcha.com):heavy_check_mark:(**免费,速度一般**) 19 | - [Recaptcha v2(谷歌)](https://developers.google.com/recaptcha):heavy_check_mark:(**国内可用,完全免费**) 20 | - [Recaptcha v3(谷歌)](https://developers.google.com/recaptcha):heavy_check_mark:(**国内可用,完全免费**) 21 | - [~~数美(暂不支持网页)~~](https://www.ishumei.com/product/bs-post-register.html) 22 | - [腾讯防水墙](https://cloud.tencent.com/document/product/1110/36839):heavy_check_mark: 23 | - [同盾](https://x.tongdun.cn/product/captcha) 24 | - [V5验证](https://www.verify5.com/index):heavy_check_mark:(**免费版日限100次**) 25 | - [Vaptcha](https://www.vaptcha.com)(**不完全免费,不过该验证码使用难度相对较高**)(**需要一个密钥来开发**) 26 | - [网易](http://dun.163.com/product/captcha) 27 | - [云片](https://www.yunpian.com/product/captcha) (**似乎存在一个奇怪的bug死活调不通**) 28 | - 有主流的未发现的、额外有需求的请[issue](https://github.com/asundust/dcat-auth-captcha/issues) 29 | 30 | > 受限制于有些验证码密钥是收费版,目前代码不能做到完全兼容 如果有好心人士提供密码 我将严格保密 仅用于开发工作 31 | 32 | > 目前不打算开发兼容1.x版本的代码 33 | 34 | ![img](https://user-images.githubusercontent.com/6573979/113974655-df986e00-9870-11eb-96c6-aa9f71b8016f.gif) 35 | 36 | ### 安装 37 | 38 | ``` 39 | composer require asundust/dcat-auth-captcha 40 | ``` 41 | - **重要说明:由于密钥配置是在后台配置,首次安装前,确保已经登录,安装后及时配置密钥,否则将出现无法登录的情况** 42 | - 若出现上述情况,请先卸载,再登录,然后安装配置,即可解决 43 | 44 | ### 获取密钥参数配置 45 | 46 | #### 顶象 47 | 48 | ##### 可配置的参数 49 | 50 | - AppId: {AppID} 51 | - Secret: {AppSecret} 52 | - 验证码样式: popup // 弹出式: popup 嵌入式: embed 内联式: inline 触发式: oneclick (不填写默认popup) 53 | - 额外配置: [] 54 | 55 | ##### 相关链接 56 | 57 | - 访问 [https://www.dingxiang-inc.com/business/captcha](https://www.dingxiang-inc.com/business/captcha) 58 | - [官网文档配置DEMO](https://cdn.dingxiang-inc.com/ctu-group/captcha-ui/demo) 59 | - [官网文档地址](https://www.dingxiang-inc.com/docs/detail/captcha) 60 | 61 | #### 极验 62 | 63 | ##### 可配置的参数 64 | 65 | - AppId: {ID} 66 | - Secret: {KEY} 67 | - 验证码样式: bind // 隐藏式: bind 弹出式: popup 浮动式: float 自定区域浮动式(与popup类似,由于登录页面无需自定区域,故效果和popup一样的): custom (不填写默认bind) 68 | - 额外配置: [] 69 | 70 | ##### 相关链接 71 | 72 | - 访问 [https://www.dingxiang-inc.com/business/captcha](https://www.dingxiang-inc.com/business/captcha) 73 | - [官网文档地址](http://docs.geetest.com/sensebot/deploy/server/php) 74 | 75 | #### hCaptcha 76 | 77 | ##### 可配置的参数 78 | 79 | - AppId: {sitekey} 80 | - Secret: {secret} 81 | - 验证码样式: invisible // 隐藏式: invisible 复选框: display (不填写默认invisible) (invisible有点bug尚未找到号的解决方案,暂不推荐使用) 82 | 83 | ##### 相关链接 84 | 85 | - 访问 [https://dashboard.hcaptcha.com/overview](https://dashboard.hcaptcha.com/overview) 86 | - [官网文档地址(前端)显示](https://docs.hcaptcha.com/configuration) 87 | - [官网文档地址(前端)隐藏](https://docs.hcaptcha.com/invisible) 88 | - [官网文档地址(后端)](https://docs.hcaptcha.com) 89 | 90 | #### Recaptcha v2(谷歌) 91 | 92 | ##### 可配置的参数 93 | 94 | - AppId: {site_key} 95 | - Secret: {secret} 96 | - 验证码样式: invisible // 隐藏式: invisible 复选框: display (不填写默认invisible) 97 | - 服务域名(可选): https://www.google.com // 服务域名,可选,无此选项默认为 https://recaptcha.net 98 | 99 | ##### 相关链接 100 | 101 | - 访问 [https://www.google.com/recaptcha/admin/create](https://www.google.com/recaptcha/admin/create) 选择v2版 102 | - 管理面板 [https://www.google.com/recaptcha/admin](https://www.google.com/recaptcha/admin) 103 | - [官网文档地址(前端)显示](https://developers.google.com/recaptcha/docs/display) 104 | - [官网文档地址(前端)隐藏](https://developers.google.com/recaptcha/docs/invisible) 105 | - [官网文档地址(后端)](https://developers.google.com/recaptcha/docs/verify/) 106 | 107 | #### Recaptcha v3(谷歌) 108 | 109 | ##### 可配置的参数 110 | 111 | - AppId: {site_key} 112 | - Secret: {secret} 113 | - 验证码样式: invisible // 隐藏式: invisible 复选框: display (不填写默认invisible) 114 | - 服务域名(可选): https://www.google.com // 服务域名,可选,无此选项默认为 https://recaptcha.net 115 | - 可信任分数(可选): 0.7 // 可信任分数,可选,无此选项默认为 0.7 116 | 117 | ##### 相关链接 118 | 119 | - 访问 [https://www.google.com/recaptcha/admin/create](https://www.google.com/recaptcha/admin/create) 选择v3版 120 | - 管理面板 [https://www.google.com/recaptcha/admin](https://www.google.com/recaptcha/admin) 121 | - [官网文档地址(前端)](https://developers.google.com/recaptcha/docs/v3) 122 | - [官网文档地址(后端)](https://developers.google.com/recaptcha/docs/verify/) 123 | 124 | #### 腾讯防水墙 125 | 126 | ##### 可配置的参数 127 | 128 | - AppId: {AppID} 129 | - Secret: {AppSecretKey} 130 | 131 | ##### 相关链接 132 | 133 | - 新用户购买 [https://cloud.tencent.com/product/captcha](https://cloud.tencent.com/product/captcha) 134 | - 新用户[官方使用文档地址](https://cloud.tencent.com/document/product/1110/36839) 135 | - 老用户[官方使用文档地址](https://007.qq.com/captcha/#/gettingStart) 136 | - [关于腾讯防水墙收费的声明(新用户终身免费5万次)](https://007.qq.com/help.html?ADTAG=index.head) 137 | 138 | #### V5验证 139 | 140 | ##### 可配置的参数 141 | 142 | - AppId: {APP ID} 143 | - Secret: {APP Key} 144 | - 主机: {Host} 145 | 146 | ##### 相关链接 147 | 148 | - 访问 [https://www.verify5.com/console/app/list](https://www.verify5.com/console/app/list) 149 | - 访问 [官方使用文档地址](https://www.verify5.com/doc/reference) 150 | 151 | #### ~~Vaptcha~~ 152 | 153 | ##### ~~可配置的参数~~ 154 | 155 | - ~~AppId: {VID}~~ 156 | - ~~Secret: {Key}~~ 157 | - ~~验证码样式: invisible // 隐藏式: invisible 点击式: click 嵌入式: embed (不填写默认invisible)~~ 158 | - ~~额外配置: []~~ 159 | 160 | ##### 相关链接 161 | 162 | - 访问 [https://www.vaptcha.com](https://www.vaptcha.com) 163 | - 访问 [官方使用文档地址](https://www.vaptcha.com/document/install) 164 | 165 | #### ~~网易易盾~~ 166 | 167 | ##### ~~可配置的参数~~ 168 | 169 | - ~~AppId: {captchaId}~~ 170 | - ~~Secret: {secretId}~~ 171 | - ~~Secret Key: {secretKey}~~ 172 | - ~~验证码样式: // 注意后台申请的类型!!! 常规弹出式: popup 常规嵌入式: embed 常规触发式: float 无感绑定按钮:bind 无感点击式: ''(留空,奇葩设定) (不填写默认popup)~~ 173 | - ~~额外配置: []~~ 174 | 175 | ##### 相关链接 176 | 177 | - 访问 [http://dun.163.com/product/captcha](http://dun.163.com/product/captcha) 178 | - 访问 [官方使用文档地址](http://support.dun.163.com/documents/15588062143475712?docId=150401879704260608) 179 | 180 | #### ~~云片~~ 181 | 182 | ##### ~~可配置的参数~~ 183 | 184 | - ~~AppId: {APPID}~~ 185 | - ~~Secret: {Secret Id}~~ 186 | - ~~Secret Key: {secretKey}~~ 187 | - ~~验证码样式: // flat: 直接嵌入 float: 浮动 dialog: 对话框 external: 外置滑动(拖动滑块时才浮现验证图片,仅适用于滑动拼图验证) (不填写默认dialog) 188 | TIP:flat和external貌似存在回调bug,不推荐使用~~ 189 | - ~~额外配置: []~~ 190 | 191 | ##### 相关链接 192 | 193 | - 访问 [https://www.yunpian.com/console/#/captcha/product](https://www.yunpian.com/console/#/captcha/product) 194 | - 访问 [官方使用文档地址](https://www.yunpian.com/official/document/sms/zh_CN/captcha/captcha_service) 195 | 196 | ### 使用 197 | 198 | 在浏览器里打开dcat-admin登录页 199 | 200 | ### 未来 201 | 202 | - 加入更多滑动验证码(持续添加ing):heavy_check_mark: 203 | - ~~验证码功能模块化,提供给Laravel项目内使用(该想法实现有点难度,看着办吧)~~ 204 | 205 | ### 升级注意事项 206 | 207 | [UPGRADE.md](UPGRADE.md) 208 | 209 | ### 更新日志 210 | 211 | [CHANGE_LOG.md](CHANGE_LOG.md) 212 | 213 | ### 鸣谢名单 214 | 215 | [de-memory](https://github.com/de-memory) 216 | 217 | ### 支持 218 | 219 | 如果觉得这个项目帮你节约了时间,不妨支持一下呗! 220 | 221 | ![alipay](https://user-images.githubusercontent.com/6573979/91679916-2c4df500-eb7c-11ea-98a7-ab740ddda77d.png) 222 | ![wechat](https://user-images.githubusercontent.com/6573979/91679913-2b1cc800-eb7c-11ea-8915-eb0eced94aee.png) 223 | 224 | ### License 225 | 226 | [The MIT License (MIT)](https://opensource.org/licenses/MIT) 227 | -------------------------------------------------------------------------------- /resources/assets/js/geetest/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 | -------------------------------------------------------------------------------- /src/Http/Controllers/DcatAuthCaptchaController.php: -------------------------------------------------------------------------------- 1 | [ 29 | 'popup' => 'login', 30 | 'embed' => 'login_style', 31 | 'inline' => 'login_style', 32 | 'oneclick' => 'login_style', 33 | ], 34 | 'geetest' => [ 35 | 'bind' => 'login', 36 | 'float' => 'login_style', 37 | 'popup' => 'login_style', 38 | 'custom' => 'login_style', 39 | ], 40 | 'hcaptcha' => [ 41 | 'invisible' => 'login', 42 | 'display' => 'login_style', 43 | ], 44 | 'recaptchav2' => [ 45 | 'invisible' => 'login', 46 | 'display' => 'login_style', 47 | ], 48 | 'recaptcha' => [ 49 | 'default' => 'login', 50 | ], 51 | 'tencent' => [ 52 | 'popup' => 'login', 53 | ], 54 | 'verify5' => [ 55 | 'default' => 'login_style', 56 | ], 57 | 'vaptcha' => [ 58 | 'invisible' => 'login', 59 | 'click' => 'login_style', 60 | 'embed' => 'login_style', 61 | ], 62 | 'wangyi' => [ 63 | 'popup' => 'login', 64 | 'float' => 'login_style', 65 | 'embed' => 'login_style', 66 | 'bind' => 'login', 67 | '' => 'login_style', 68 | ], 69 | 'yunpian' => [ 70 | 'flat' => 'login_style', 71 | 'float' => 'login_style', 72 | 'dialog' => 'login_style', 73 | 'external' => 'login_style', 74 | ], 75 | ]; 76 | 77 | /** 78 | * AuthCaptchaController constructor. 79 | */ 80 | public function __construct() 81 | { 82 | $this->captchaProvider = DcatAuthCaptchaServiceProvider::setting('provider'); 83 | $this->captchaAppid = DcatAuthCaptchaServiceProvider::setting('appid'); 84 | $this->captchaSecret = DcatAuthCaptchaServiceProvider::setting('secret'); 85 | $this->captchaStyle = DcatAuthCaptchaServiceProvider::setting('style'); 86 | 87 | $throttle = DcatAuthCaptchaServiceProvider::setting('login_try_throttle'); 88 | if ($throttle) { 89 | $version = app()->make('\Mnabialek\LaravelVersion\Version'); 90 | $arr = explode('.', $version->get()); 91 | if ($arr[0] >= 8) { 92 | $this->middleware(DcatAuthCaptchaThrottleMiddleware::class . ':' . $throttle)->only('postLogin'); 93 | } else { 94 | $this->middleware(DcatAuthCaptchaThrottleMiddlewareBelow8::class . ':' . $throttle)->only('postLogin'); 95 | } 96 | } 97 | } 98 | 99 | /** 100 | * Get Login. 101 | * 102 | * @return Content|\Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector 103 | * 104 | * @throws \GuzzleHttp\Exception\GuzzleException 105 | */ 106 | public function getLogin(Content $content) 107 | { 108 | if ($this->guard()->check()) { 109 | return redirect($this->getRedirectPath()); 110 | } 111 | 112 | $extConfig = []; 113 | $json = DcatAuthCaptchaServiceProvider::setting('ext_config'); 114 | if ($json) { 115 | $result = json_decode($json, true); 116 | if ($result) { 117 | $extConfig = $result; 118 | } 119 | } 120 | 121 | switch ($this->captchaProvider) { 122 | case 'dingxiang': 123 | case 'tencent': 124 | if (!$this->captchaStyle) { 125 | $this->captchaStyle = 'popup'; 126 | } 127 | 128 | break; 129 | case 'geetest': 130 | if (!$this->captchaStyle) { 131 | $this->captchaStyle = 'bind'; 132 | } 133 | Admin::headerJs('vendor/dcat-admin-extensions/asundust/dcat-auth-captcha/js/geetest/gt.js'); 134 | $extConfig['initData'] = $this->getGeetestStatus(); 135 | 136 | break; 137 | case 'hcaptcha': 138 | case 'vaptcha': 139 | if (!$this->captchaStyle) { 140 | $this->captchaStyle = 'invisible'; 141 | } 142 | 143 | break; 144 | case 'recaptchav2': 145 | if (!$this->captchaStyle) { 146 | $this->captchaStyle = 'invisible'; 147 | $extConfig['initData']['domain'] = DcatAuthCaptchaServiceProvider::setting('domain'); 148 | } 149 | 150 | break; 151 | case 'recaptcha': 152 | if (!$this->captchaStyle) { 153 | $this->captchaStyle = 'default'; 154 | $extConfig['initData']['domain'] = DcatAuthCaptchaServiceProvider::setting('domain'); 155 | } 156 | 157 | break; 158 | case 'verify5': 159 | $extConfig['token'] = $this->getVerify5Token(); 160 | $this->captchaStyle = 'default'; 161 | $extConfig['initData']['host'] = DcatAuthCaptchaServiceProvider::setting('host'); 162 | 163 | break; 164 | case 'wangyi': 165 | if (null === $this->captchaStyle) { 166 | $this->captchaStyle = 'popup'; 167 | } 168 | 169 | break; 170 | case 'yunpian': 171 | if (!$this->captchaStyle) { 172 | $this->captchaStyle = 'dialog'; 173 | } 174 | 175 | break; 176 | 177 | default: 178 | break; 179 | } 180 | 181 | return $content->full()->body(view(DcatAuthCaptchaServiceProvider::instance()->getName() . '::' . $this->captchaProvider . '.' . $this->providerStyles[$this->captchaProvider][$this->captchaStyle], [ 182 | 'captchaAppid' => $this->captchaAppid, 183 | 'captchaStyle' => $this->captchaStyle, 184 | 'extConfig' => $extConfig, 185 | ])); 186 | } 187 | 188 | /** 189 | * Get Geetest Status. 190 | * 191 | * @throws \GuzzleHttp\Exception\GuzzleException 192 | */ 193 | private function getGeetestStatus(): array 194 | { 195 | $clientType = Agent::isMobile() ? 'h5' : 'web'; 196 | session(['GeetestAuth-client_type' => $clientType]); 197 | $params = [ 198 | 'client_type' => $clientType, 199 | 'gt' => $this->captchaAppid, 200 | 'ip_address' => request()->ip(), 201 | 'new_captcha' => 1, 202 | 'user_id' => '', 203 | ]; 204 | $url = 'http://api.geetest.com/register.php?' . http_build_query($params); 205 | $response = $this->captchaHttp()->get($url); 206 | $statusCode = $response->getStatusCode(); 207 | $contents = $response->getBody()->getContents(); 208 | if (200 != $statusCode) { 209 | return $this->geetestFailProcess(); 210 | } 211 | if (32 != strlen($contents)) { 212 | return $this->geetestFailProcess(); 213 | } 214 | 215 | return $this->geetestSuccessProcess($contents); 216 | } 217 | 218 | /** 219 | * Geetest Success Process. 220 | * 221 | * @param $challenge 222 | */ 223 | private function geetestSuccessProcess($challenge): array 224 | { 225 | $challenge = md5($challenge . $this->captchaSecret); 226 | $result = [ 227 | 'success' => 1, 228 | 'gt' => $this->captchaAppid, 229 | 'challenge' => $challenge, 230 | 'new_captcha' => 1, 231 | ]; 232 | session(['GeetestAuth-gtserver' => 1, 'GeetestAuth-user_id' => '']); 233 | 234 | return $result; 235 | } 236 | 237 | /** 238 | * Geetest Fail Process. 239 | */ 240 | private function geetestFailProcess(): array 241 | { 242 | $rnd1 = md5(rand(0, 100)); 243 | $rnd2 = md5(rand(0, 100)); 244 | $challenge = $rnd1 . substr($rnd2, 0, 2); 245 | $result = [ 246 | 'success' => 0, 247 | 'gt' => $this->captchaAppid, 248 | 'challenge' => $challenge, 249 | 'new_captcha' => 1, 250 | ]; 251 | session(['GeetestAuth-gtserver' => 0, 'GeetestAuth-user_id' => 0]); 252 | 253 | return $result; 254 | } 255 | 256 | /** 257 | * Get Verify5 Token. 258 | * 259 | * @return mixed|string 260 | * 261 | * @throws \GuzzleHttp\Exception\GuzzleException 262 | */ 263 | private function getVerify5Token() 264 | { 265 | $params = [ 266 | 'appid' => $this->captchaAppid, 267 | 'timestamp' => now()->timestamp . '000', 268 | ]; 269 | $params['signature'] = $this->getSignature($this->captchaSecret, $params); 270 | $url = 'https://' . DcatAuthCaptchaServiceProvider::setting('host') . '/openapi/getToken?' . http_build_query($params); 271 | $response = $this->captchaHttp()->get($url); 272 | $statusCode = $response->getStatusCode(); 273 | $contents = $response->getBody()->getContents(); 274 | if (200 != $statusCode) { 275 | return ''; 276 | } 277 | $result = json_decode($contents, true); 278 | if (true != $result['success']) { 279 | return ''; 280 | } 281 | 282 | return $result['data']['token']; 283 | } 284 | 285 | /** 286 | * Post Login. 287 | * 288 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|mixed|\Symfony\Component\HttpFoundation\Response 289 | * 290 | * @throws \GuzzleHttp\Exception\GuzzleException 291 | */ 292 | public function postLogin(Request $request) 293 | { 294 | switch ($this->captchaProvider) { 295 | case 'dingxiang': 296 | return $this->captchaValidateDingxiang($request); 297 | 298 | break; 299 | case 'geetest': 300 | return $this->captchaValidateGeetest($request); 301 | 302 | break; 303 | case 'hcaptcha': 304 | return $this->captchaValidateHCaptcha($request); 305 | 306 | break; 307 | case 'recaptchav2': 308 | case 'recaptcha': 309 | return $this->captchaValidateRecaptcha($request); 310 | 311 | break; 312 | case 'tencent': 313 | return $this->captchaValidateTencent($request); 314 | 315 | break; 316 | case 'verify5': 317 | return $this->captchaValidateVerify5($request); 318 | 319 | break; 320 | case 'vaptcha': 321 | return $this->captchaValidateVaptcha($request); 322 | 323 | break; 324 | case 'wangyi': 325 | return $this->captchaValidateWangyi($request); 326 | 327 | break; 328 | case 'yunpian': 329 | return $this->captchaValidateYunpian($request); 330 | 331 | break; 332 | 333 | default: 334 | return back()->withInput()->withErrors(['captcha' => $this->toTrans('config')]); 335 | 336 | break; 337 | } 338 | } 339 | 340 | /** 341 | * Dingxiang Captcha. 342 | * 343 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 344 | * 345 | * @throws \GuzzleHttp\Exception\GuzzleException 346 | */ 347 | private function captchaValidateDingxiang(Request $request) 348 | { 349 | $token = $request->input('token', ''); 350 | if (!$token) { 351 | return $this->captchaErrorResponse($this->toTrans('fail')); 352 | } 353 | $tokenArr = array_filter(explode(':', $token)); 354 | if (2 != count($tokenArr)) { 355 | return $this->captchaErrorResponse($this->toTrans('fail')); 356 | } 357 | 358 | $params = [ 359 | 'appKey' => $this->captchaAppid, 360 | 'constId' => $tokenArr[1], 361 | 'sign' => md5($this->captchaSecret . $tokenArr[0] . $this->captchaSecret), 362 | 'token' => $tokenArr[0], 363 | ]; 364 | 365 | $url = 'https://cap.dingxiang-inc.com/api/tokenVerify'; 366 | $response = $this->captchaHttp()->get($url . '?' . http_build_query($params)); 367 | $statusCode = $response->getStatusCode(); 368 | $contents = $response->getBody()->getContents(); 369 | 370 | if (200 != $statusCode) { 371 | return $this->captchaErrorResponse($this->toTrans('fail')); 372 | } 373 | $result = json_decode($contents, true); 374 | if (true === $result['success']) { 375 | return $this->loginValidate($request); 376 | } 377 | 378 | return $this->validationErrorsResponse([ 379 | 'captcha' => $this->toTrans('fail'), 380 | ]); 381 | } 382 | 383 | /** 384 | * Geetest Captcha. 385 | * 386 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 387 | * 388 | * @throws \GuzzleHttp\Exception\GuzzleException 389 | */ 390 | private function captchaValidateGeetest(Request $request) 391 | { 392 | $geetestChallenge = $request->input('geetest_challenge', ''); 393 | $geetestValidate = $request->input('geetest_validate', ''); 394 | $geetestSeccode = $request->input('geetest_seccode', ''); 395 | if (!$geetestChallenge || !$geetestValidate || !$geetestSeccode) { 396 | return $this->captchaErrorResponse($this->toTrans('fail')); 397 | } 398 | 399 | if (1 != session('GeetestAuth-gtserver')) { 400 | if (md5($geetestChallenge) == $geetestValidate) { 401 | return $this->loginValidate($request); 402 | } 403 | 404 | return $this->captchaErrorResponse($this->toTrans('fail')); 405 | } 406 | 407 | $params = [ 408 | 'challenge' => $geetestChallenge, 409 | 'client_type' => session('GeetestAuth-client_type'), 410 | 'gt' => $this->captchaAppid, 411 | 'ip_address' => $request->ip(), 412 | 'json_format' => 1, 413 | 'new_captcha' => 1, 414 | 'sdk' => 'php_3.0.0', 415 | 'seccode' => $geetestSeccode, 416 | 'user_id' => session('GeetestAuth-user_id'), 417 | 'validate' => $geetestValidate, 418 | ]; 419 | 420 | $url = 'http://api.geetest.com/validate.php'; 421 | $response = $this->captchaHttp()->post($url, [ 422 | 'form_params' => $params, 423 | ]); 424 | $statusCode = $response->getStatusCode(); 425 | $contents = $response->getBody()->getContents(); 426 | if (200 != $statusCode) { 427 | return $this->captchaErrorResponse($this->toTrans('fail')); 428 | } 429 | $result = json_decode($contents, true); 430 | if (is_array($result) && $result['seccode'] == md5($geetestSeccode)) { 431 | return $this->loginValidate($request); 432 | } 433 | 434 | return $this->validationErrorsResponse([ 435 | 'captcha' => $this->toTrans('fail'), 436 | ]); 437 | } 438 | 439 | /** 440 | * HCaptcha Captcha. 441 | * 442 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 443 | * 444 | * @throws \GuzzleHttp\Exception\GuzzleException 445 | */ 446 | private function captchaValidateHCaptcha(Request $request) 447 | { 448 | $token = $request->input('token', ''); 449 | if (!$token) { 450 | return $this->captchaErrorResponse($this->toTrans('fail')); 451 | } 452 | 453 | $params = [ 454 | 'secret' => $this->captchaSecret, 455 | 'response' => $token, 456 | 'remoteip' => $request->ip(), 457 | ]; 458 | 459 | $url = 'https://hcaptcha.com/siteverify'; 460 | $response = $this->captchaHttp()->post($url, [ 461 | 'form_params' => $params, 462 | ]); 463 | $statusCode = $response->getStatusCode(); 464 | $contents = $response->getBody()->getContents(); 465 | if (200 != $statusCode) { 466 | return $this->captchaErrorResponse($this->toTrans('fail')); 467 | } 468 | $result = json_decode($contents, true); 469 | if (true === $result['success']) { 470 | return $this->loginValidate($request); 471 | } 472 | 473 | return $this->validationErrorsResponse([ 474 | 'captcha' => $this->toTrans('fail'), 475 | ]); 476 | } 477 | 478 | /** 479 | * Recaptcha Captcha. 480 | * 481 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 482 | * 483 | * @throws \GuzzleHttp\Exception\GuzzleException 484 | */ 485 | private function captchaValidateRecaptcha(Request $request) 486 | { 487 | $token = $request->input('token', ''); 488 | if (!$token) { 489 | return $this->captchaErrorResponse($this->toTrans('fail')); 490 | } 491 | 492 | $params = [ 493 | 'secret' => $this->captchaSecret, 494 | 'response' => $token, 495 | 'remoteip' => $request->ip(), 496 | ]; 497 | 498 | $url = rtrim(DcatAuthCaptchaServiceProvider::setting('domain') ?? 'https://recaptcha.net') . '/recaptcha/api/siteverify'; 499 | $response = $this->captchaHttp()->post($url, [ 500 | 'form_params' => $params, 501 | ]); 502 | $statusCode = $response->getStatusCode(); 503 | $contents = $response->getBody()->getContents(); 504 | if (200 != $statusCode) { 505 | return $this->captchaErrorResponse($this->toTrans('fail')); 506 | } 507 | $result = json_decode($contents, true); 508 | if ('recaptcha' == $this->captchaProvider) { 509 | if (true === $result['success'] && $result['score'] >= DcatAuthCaptchaServiceProvider::setting('score') ?? 0.7) { 510 | return $this->loginValidate($request); 511 | } 512 | } else { 513 | if (true === $result['success']) { 514 | return $this->loginValidate($request); 515 | } 516 | } 517 | 518 | return $this->validationErrorsResponse([ 519 | 'captcha' => $this->toTrans('fail'), 520 | ]); 521 | } 522 | 523 | /** 524 | * Tencent Captcha. 525 | * 526 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 527 | * 528 | * @throws \GuzzleHttp\Exception\GuzzleException 529 | */ 530 | private function captchaValidateTencent(Request $request) 531 | { 532 | $ticket = $request->input('ticket', ''); 533 | $randstr = $request->input('randstr', ''); 534 | if (!$ticket || !$randstr) { 535 | return $this->captchaErrorResponse($this->toTrans('fail')); 536 | } 537 | 538 | $params = [ 539 | 'aid' => $this->captchaAppid, 540 | 'AppSecretKey' => $this->captchaSecret, 541 | 'Ticket' => $ticket, 542 | 'Randstr' => $randstr, 543 | 'UserIP' => $request->getClientIp(), 544 | ]; 545 | 546 | $url = 'https://ssl.captcha.qq.com/ticket/verify'; 547 | $response = $this->captchaHttp()->get($url . '?' . http_build_query($params)); 548 | $statusCode = $response->getStatusCode(); 549 | $contents = $response->getBody()->getContents(); 550 | 551 | if (200 != $statusCode) { 552 | return $this->captchaErrorResponse($this->toTrans('fail')); 553 | } 554 | $result = json_decode($contents, true); 555 | if (1 != $result['response']) { 556 | return $this->captchaErrorResponse($this->toTrans('fail')); 557 | } 558 | 559 | return $this->loginValidate($request); 560 | } 561 | 562 | /** 563 | * Verify5 Captcha. 564 | * 565 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 566 | * 567 | * @throws \GuzzleHttp\Exception\GuzzleException 568 | */ 569 | private function captchaValidateVerify5(Request $request) 570 | { 571 | $token = $request->input('token', ''); 572 | $verify5Token = $request->input('verify5_token', ''); 573 | if (!$token || !$verify5Token) { 574 | return $this->captchaErrorResponse($this->toTrans('fail')); 575 | } 576 | 577 | $params = [ 578 | 'host' => DcatAuthCaptchaServiceProvider::setting('host'), 579 | 'verifyid' => $token, 580 | 'token' => $verify5Token, 581 | 'timestamp' => now()->timestamp . '000', 582 | ]; 583 | $params['signature'] = $this->getSignature($this->captchaSecret, $params); 584 | $url = 'https://' . DcatAuthCaptchaServiceProvider::setting('host') . '/openapi/verify?' . http_build_query($params); 585 | $response = $this->captchaHttp()->get($url); 586 | $statusCode = $response->getStatusCode(); 587 | $contents = $response->getBody()->getContents(); 588 | if (200 != $statusCode) { 589 | return $this->captchaErrorResponse($this->toTrans('fail')); 590 | } 591 | $result = json_decode($contents, true); 592 | if (true != $result['success']) { 593 | return $this->captchaErrorResponse($this->toTrans('fail')); 594 | } 595 | 596 | return $this->loginValidate($request); 597 | } 598 | 599 | /** 600 | * Vaptcha Captcha. 601 | * 602 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 603 | * 604 | * @throws \GuzzleHttp\Exception\GuzzleException 605 | */ 606 | private function captchaValidateVaptcha(Request $request) 607 | { 608 | $token = $request->input('token', ''); 609 | if (!$token) { 610 | return $this->captchaErrorResponse($this->toTrans('fail')); 611 | } 612 | 613 | $params = [ 614 | 'id' => $this->captchaAppid, 615 | 'secretkey' => $this->captchaSecret, 616 | 'token' => $token, 617 | 'ip' => $request->ip(), 618 | ]; 619 | 620 | $url = 'http://0.vaptcha.com/verify'; 621 | $response = $this->captchaHttp()->post($url, [ 622 | 'form_params' => $params, 623 | ]); 624 | $statusCode = $response->getStatusCode(); 625 | $contents = $response->getBody()->getContents(); 626 | 627 | if (200 != $statusCode) { 628 | return $this->captchaErrorResponse($this->toTrans('fail')); 629 | } 630 | $result = json_decode($contents, true); 631 | if (1 != $result['success']) { 632 | return $this->captchaErrorResponse($this->toTrans('fail')); 633 | } 634 | 635 | return $this->loginValidate($request); 636 | } 637 | 638 | /** 639 | * Wangyi Captcha. 640 | * 641 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 642 | * 643 | * @throws \GuzzleHttp\Exception\GuzzleException 644 | */ 645 | private function captchaValidateWangyi(Request $request) 646 | { 647 | $token = $request->input('token', ''); 648 | if (!$token) { 649 | return $this->captchaErrorResponse($this->toTrans('fail')); 650 | } 651 | 652 | $secretKey = config('admin.extensions.dcat-auth-captcha.secret_key', ''); 653 | if (!$secretKey) { 654 | return back()->withInput()->withErrors(['captcha' => $this->toTrans('config')]); 655 | } 656 | 657 | $params = [ 658 | 'captchaId' => $this->captchaAppid, 659 | 'validate' => $token, 660 | 'user' => '', 661 | 'secretId' => $this->captchaSecret, 662 | 'version' => 'v2', 663 | 'timestamp' => now()->timestamp . '000', 664 | 'nonce' => Str::random(), 665 | ]; 666 | 667 | $params['signature'] = $this->getSignature($secretKey, $params); 668 | 669 | $url = 'http://c.dun.163yun.com/api/v2/verify'; 670 | $response = $this->captchaHttp()->post($url, [ 671 | 'form_params' => $params, 672 | ]); 673 | $statusCode = $response->getStatusCode(); 674 | $contents = $response->getBody()->getContents(); 675 | 676 | if (200 != $statusCode) { 677 | return $this->captchaErrorResponse($this->toTrans('fail')); 678 | } 679 | $result = json_decode($contents, true); 680 | if (true === $result['result']) { 681 | return $this->loginValidate($request); 682 | } 683 | 684 | return $this->validationErrorsResponse([ 685 | 'captcha' => $this->toTrans('fail'), 686 | ]); 687 | } 688 | 689 | /** 690 | * Yunpian Captcha. 691 | * 692 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 693 | * 694 | * @throws \GuzzleHttp\Exception\GuzzleException 695 | */ 696 | private function captchaValidateYunpian(Request $request) 697 | { 698 | $token = $request->input('token', ''); 699 | $authenticate = $request->input('authenticate', ''); 700 | if (!$token || !$authenticate) { 701 | return $this->captchaErrorResponse($this->toTrans('fail')); 702 | } 703 | 704 | $secretKey = config('admin.extensions.dcat-auth-captcha.secret_key', ''); 705 | if (!$secretKey) { 706 | return back()->withInput()->withErrors(['captcha' => $this->toTrans('config')]); 707 | } 708 | 709 | $params = [ 710 | 'authenticate' => $authenticate, 711 | 'captchaId' => $this->captchaAppid, 712 | 'token' => $token, 713 | 'secretId' => $this->captchaSecret, 714 | 'user' => '', 715 | 'version' => '1.0', 716 | 'timestamp' => now()->timestamp . '000', 717 | 'nonce' => Str::random(), 718 | ]; 719 | 720 | $params['signature'] = $this->getSignature($secretKey, $params); 721 | 722 | $url = 'https://captcha.yunpian.com/v1/api/authenticate'; 723 | $response = $this->captchaHttp()->post($url, [ 724 | 'form_params' => $params, 725 | ]); 726 | $statusCode = $response->getStatusCode(); 727 | $contents = $response->getBody()->getContents(); 728 | if (200 != $statusCode) { 729 | return $this->captchaErrorResponse($this->toTrans('fail')); 730 | } 731 | $result = json_decode($contents, true); 732 | if (0 === $result['code'] && 'ok' == $result['msg']) { 733 | return $this->loginValidate($request); 734 | } 735 | 736 | return $this->validationErrorsResponse([ 737 | 'captcha' => $this->toTrans('fail'), 738 | ]); 739 | } 740 | 741 | /** 742 | * 生成签名信息. 743 | * 744 | * @param $secretKey 745 | * @param $params 746 | */ 747 | private function getSignature($secretKey, $params): string 748 | { 749 | ksort($params); 750 | $str = ''; 751 | foreach ($params as $key => $value) { 752 | $str .= $key . $value; 753 | } 754 | $str .= $secretKey; 755 | 756 | return md5($str); 757 | } 758 | 759 | /** 760 | * Login Validate. 761 | * 762 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response 763 | */ 764 | private function loginValidate(Request $request) 765 | { 766 | $credentials = $request->only([$this->username(), 'password']); 767 | $remember = (bool) $request->input('remember', false); 768 | 769 | /** @var \Illuminate\Validation\Validator $validator */ 770 | $validator = Validator::make($credentials, [ 771 | $this->username() => 'required', 772 | 'password' => 'required', 773 | ]); 774 | 775 | if ($validator->fails()) { 776 | return $this->validationErrorsResponse($validator); 777 | } 778 | 779 | if ($this->guard()->attempt($credentials, $remember)) { 780 | return $this->sendLoginResponse($request); 781 | } 782 | 783 | return $this->validationErrorsResponse([ 784 | $this->username() => $this->getFailedLoginMessage(), 785 | ]); 786 | } 787 | 788 | /** 789 | * Http. 790 | */ 791 | private function captchaHttp(): Client 792 | { 793 | return new Client([ 794 | 'timeout' => DcatAuthCaptchaServiceProvider::setting('timeout', 5), 795 | 'verify' => false, 796 | 'http_errors' => false, 797 | ]); 798 | } 799 | 800 | /** 801 | * getErrorMessage. 802 | * 803 | * @param $type 804 | */ 805 | private function toTrans($type): ?string 806 | { 807 | return DcatAuthCaptchaServiceProvider::trans('dcat-auth-captcha.messages.' . $type); 808 | } 809 | 810 | /** 811 | * CaptchaErrorResponse. 812 | * 813 | * @param $message 814 | * 815 | * @return \Illuminate\Http\JsonResponse 816 | */ 817 | private function captchaErrorResponse($message) 818 | { 819 | return $this->response() 820 | ->error($message) 821 | ->locationToIntended($this->getRedirectPath()) 822 | ->send(); 823 | } 824 | } 825 | --------------------------------------------------------------------------------