├── 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 | --------------------------------------------------------------------------------