├── 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 |
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 |
5 | @if($errors->has('captcha'))
6 | @foreach($errors->get('captcha') as $message)
7 |
9 | @endforeach
10 | @endif
11 |
12 |
12 | @if($errors->has('captcha'))
13 | @foreach($errors->get('captcha') as $message)
14 |
16 | @endforeach
17 | @endif
18 |
19 |
30 |
31 |
32 | {{ config('admin.name') }}
33 |
34 |
35 |
36 |
{{ __('admin.welcome_back') }}
37 |
38 |
102 |
103 |
104 |
105 |
106 |
107 |
119 | @yield('js')
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Dcat-Admin登录 滑动验证插件 多平台支持
2 | ======
3 |
4 | 
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 | 
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 | 
222 | 
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 |
--------------------------------------------------------------------------------