├── README.md
├── LICENSE
└── Plugin.php
/README.md:
--------------------------------------------------------------------------------
1 | 插件设计的很糟糕, 容易导致Google认为网站正在遭受攻击导致所有验证都不被通过, 请不要使用了.
2 | 代码留作参考.
3 |
4 | ## Google reCAPTCHA v3 Login/Comment Protect
5 |
6 | ### 关于
7 | 本插件能在登陆后台/评论时进行人机验证,基于`Google reCAPTCHA v3`,实现无交互的人机验证.
8 | 完美兼容绝大部分Pjax换页/Ajax评论主题,无需额外修改.
9 |
10 | ### 使用方法
11 | 1. 下载[本仓库](https://github.com/KawaiiZapic/Typecho-reCAPTCHA-v3/archive/master.zip)
12 | 2. 解压,更名文件夹为`GrCv3Protect`
13 | 3. 在Typecho后台启用,并填写申请的`siteKey`和`secretKey`
14 | 4. 登录保护是自动配置的.
15 | 如果需要对评论启用保护,请在评论表单处添加一行``.
16 | 某些主题可能存在其他问题(头部不输出/自行接管评论系统/ajax提交表单不完整),请咨询其作者或禁用保护.
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Zapic
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.
--------------------------------------------------------------------------------
/Plugin.php:
--------------------------------------------------------------------------------
1 | "https://www.google.com",
16 | "recaptcha" => "https://recaptcha.net"
17 | ];
18 |
19 | public static function activate() {
20 | Typecho_Plugin::factory('admin/footer.php')->end = array(__CLASS__, 'LoginScriptLoader');
21 | Typecho_Plugin::factory('Widget_Archive')->header = array(__CLASS__, 'ArchiveScriptLoader');
22 | Typecho_Plugin::factory('Widget_User')->login = array(__CLASS__, 'loginAction');
23 | Typecho_Plugin::factory('Widget_Feedback')->comment = array(__CLASS__, 'commentAction');
24 | }
25 |
26 | public static function deactivate() {}
27 |
28 | public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
29 |
30 | public static function config(Typecho_Widget_Helper_Form $form) {
31 | $key = new Typecho_Widget_Helper_Form_Element_Text('key', NULL, '', 'Site Key');
32 | $secret = new Typecho_Widget_Helper_Form_Element_Text('secret', NULL, '', 'Secret Key');
33 | $Score = new Typecho_Widget_Helper_Form_Element_Checkbox('Protect', ['login' => "登录",'comment' => "评论"], ["login"], '对以下行为启用reCAPTCHA验证');
34 | $Protect = new Typecho_Widget_Helper_Form_Element_Text('score', NULL, '0.5', 'reCAPTCHA 验证分数阈值');
35 | $jsMirror = new Typecho_Widget_Helper_Form_Element_Radio('jsMirror', ['1' => "recaptcha.net(国内可用)", '0' => "Google.com"], '1', 'reCAPTCHA 资源加载地址');
36 | $serverMirror = new Typecho_Widget_Helper_Form_Element_Radio('serverMirror', ['1' => "recaptcha.net(国内可用)", '0' => "Google.com"], '1', 'reCAPTCHA 验证地址');
37 | $forceAsync = new Typecho_Widget_Helper_Form_Element_Checkbox('forceAsync', ['login' => "登录",'comment' => "评 论"], [], '异步加载reCAPTCHA脚本');
38 | echo '在Google reCAPTCHA 添加站点以获取 Site Key & Secret key
若启用评论验证,请在主题评论表单内添加相应代码:
<?php '.__CLASS__.'::OutputCode(); ?>'; 39 | $key->input->setAttribute("autocomplete", "off"); 40 | $secret->input->setAttribute("autocomplete", "off"); 41 | $form->addInput($key); 42 | $form->addInput($secret); 43 | $form->addInput($Score); 44 | $form->addInput($Protect); 45 | $form->addInput($jsMirror); 46 | $form->addInput($serverMirror); 47 | $form->addInput($forceAsync); 48 | } 49 | 50 | public static function LoginScriptLoader() { 51 | $user = Typecho_Widget::widget('Widget_User'); 52 | if ($user->hasLogin() && $user->pass('administrator', true)) { 53 | return; 54 | } 55 | $config = Helper::options()->plugin('GrCv3Protect'); 56 | if (!is_array($config->Protect) || !in_array("login",$config->Protect) || empty($config->key) || empty($config->secret)) { 57 | return; 58 | } 59 | $url = Typecho_Common::url("recaptcha/api.js",self::$mirror[$config->jsMirror == 1 ? "recaptcha" : "google"]); 60 | $key = $config->key; 61 | $async = (is_array($config->forceAsync) && in_array("login",$config->forceAsync)) ? 'async="async"' : '' ; 62 | echo 63 | ' 64 | '; 73 | } 74 | 75 | public static function ArchiveScriptLoader(){ 76 | $user = Typecho_Widget::widget('Widget_User'); 77 | if ($user->hasLogin() && $user->pass('administrator', true)) { 78 | return; 79 | } 80 | $config = Helper::options()->plugin('GrCv3Protect'); 81 | if (!is_array($config->Protect) || !in_array("comment",$config->Protect) || empty($config->key) || empty($config->secret)) { 82 | return; 83 | } 84 | $key = $config->key; 85 | $url = Typecho_Common::url("recaptcha/api.js?render={$config->key}",self::$mirror[$config->jsMirror == 1 ? "recaptcha" : "google"]); 86 | $async = (is_array($config->forceAsync) && in_array("comment",$config->forceAsync)) ? 'async="async"' : '' ; 87 | echo 88 | " 89 | 90 | "; 91 | } 92 | 93 | public static function OutputCode() { 94 | $user = Typecho_Widget::widget('Widget_User'); 95 | if ($user->hasLogin() && $user->pass('administrator', true)) { 96 | return; 97 | } 98 | $config = Helper::options()->plugin('GrCv3Protect'); 99 | if (!is_array($config->Protect) || !in_array("comment",$config->Protect) || empty($config->key) || empty($config->secret)) { 100 | return; 101 | } 102 | $rid = rand(0,1000000); 103 | echo 104 | ' 105 | 122 | '; 123 | } 124 | 125 | public static function loginAction($name, $password, $temporarily = false, $expire = 0) { 126 | $user = Typecho_Widget::widget('Widget_User'); 127 | $config = Helper::options()->plugin('GrCv3Protect'); 128 | if (is_array($config->Protect) && in_array("login",$config->Protect) && !empty($config->key) && !empty($config->secret)) { 129 | $res = $user->request->from('g-recaptcha-response'); 130 | $url = self::$mirror[$config->serverMirror == 1 ? "recaptcha" : "google"]; 131 | $score = floatval($config->score); 132 | if (empty($res) || empty($res['g-recaptcha-response']) || self::Verify($url, $config->secret, $res['g-recaptcha-response'], $score) !== true) { 133 | $user->widget('Widget_Notice')->set('无法验证 reCAPTCHA,请重试.', 'error'); 134 | $user->response->goBack(); 135 | } 136 | } 137 | Typecho_Plugin::deactivate('GrCv3Protect'); 138 | return $user->login($name, $password, $temporarily, $expire); 139 | } 140 | 141 | public static function commentAction($comments, $obj) { 142 | $user = $obj->widget('Widget_User'); 143 | if ($user->hasLogin() && $user->pass('administrator', true)) { 144 | return $comments; 145 | } 146 | $config = Helper::options()->plugin('GrCv3Protect'); 147 | if(!is_array($config->Protect) || !in_array("comment",$config->Protect) || empty($config->key) || empty($config->secret)){ 148 | return $comments; 149 | } 150 | $url = self::$mirror[$config->serverMirror == 1 ? "recaptcha" : "google"]; 151 | $res = $user->request->from('g-recaptcha-response'); 152 | $score = floatval($config->score); 153 | if (!empty($res) && !empty($res['g-recaptcha-response']) && self::Verify($url,$config->secret,$res['g-recaptcha-response'], $score)) { 154 | return $comments; 155 | } else { 156 | throw new Typecho_Widget_Exception('无法验证 reCAPTCHA,请尝试刷新页面.'); 157 | } 158 | } 159 | public static function Verify($url, $secret, $res,$score = 0.5) { 160 | $url = Typecho_Common::url('recaptcha/api/siteverify', $url); 161 | $ch = curl_init(); 162 | curl_setopt_array($ch, [ 163 | CURLOPT_URL => $url, 164 | CURLOPT_POST => true, 165 | CURLOPT_RETURNTRANSFER => true, 166 | CURLOPT_SSL_VERIFYPEER => false, 167 | CURLOPT_SSL_VERIFYHOST => false, 168 | CURLOPT_TIMEOUT => 5, 169 | CURLOPT_POSTFIELDS => http_build_query(['secret' => $secret, 'response' => $res]) 170 | ]); 171 | @$data = curl_exec($ch); 172 | @$data = @json_decode($data, true); 173 | return (is_array($data) && $data['success'] === true && $data['score'] > $score); 174 | } 175 | } 176 | --------------------------------------------------------------------------------