Typecho 后台登录添加 reCAPTCHA

2023 年 8 月 1 日 星期二(已编辑)
/ , , ,
45
1
AI 生成的摘要
这篇文章上次修改于 2024 年 2 月 15 日 星期四,可能部分内容已经不适用,如有疑问可询问作者。

Typecho 后台登录添加 reCAPTCHA

前言

为了更好的保护我们的 Typecho 博客,我选择给 Typecho 的后台登录界面/注册界面添加一个谷歌的 reCAPTCHA 的验证码服务

教程

获取 reCAPTCHA

提示:此步操作需要用到谷歌账号和科学上网,如果如果没有条件可以在评论区私密评论让博主帮你申请(博主将通过评论区和评论时预留的邮箱发送所需内容)

首先,打开reCAPTCHA 官网

1

1

然后点击上方的 v3 Admin Console
登录你的谷歌账号

2

2

然后,打开这个链接

3

3

标签,随便写(比如某开放世界游戏)

reCAPTCHA 类型,选择reCAPTCHA 第二版"进行人机身份验证"复选框

往下滑找到域名,里面填写你博客的域名,你也可以多添加几个你需要的

然后滑到最下面点击提交

然后你就可以获得你的SiteKeySecert Key

4

4

把这两串密钥做好标记保存下来,后面要用

安装 reCAPTCHA

因为后台要改的东西很多,方便萌新,直接把下面的东西覆盖进去就行

登录页面

将以下内容覆盖到 /admin/login.php

<?php
include 'common.php';

if ($user->hasLogin()) {
    $response->redirect($options->adminUrl);
}
$rememberName = htmlspecialchars(Typecho_Cookie::get('__typecho_remember_name'));
Typecho_Cookie::delete('__typecho_remember_name');

$bodyClass = 'body-100';

include 'header.php';
?>
<div class="typecho-login-wrap">
    <div class="typecho-login">
        <h1><a href="http://typecho.org" class="i-logo">Typecho</a></h1>
        <form action="<?php $options->loginAction(); ?>" method="post" name="login" role="form">
            <p>
                <label for="name" class="sr-only"><?php _e('用户名'); ?></label>
                <input type="text" id="name" name="name" value="<?php echo $rememberName; ?>" placeholder="<?php _e('用户名'); ?>" class="text-l w-100" autofocus />
            </p>
            <p>
                <label for="password" class="sr-only"><?php _e('密码'); ?></label>
                <input type="password" id="password" name="password" class="text-l w-100" placeholder="<?php _e('密码'); ?>" />
            </p>
            <p class="submit">
                <button type="submit" class="btn btn-l w-100 primary"><?php _e('登录'); ?></button>
                <input type="hidden" name="referer" value="<?php echo htmlspecialchars($request->get('referer')); ?>" />
            </p>

            <script src="https://www.recaptcha.net/recaptcha/api.js"></script> <!-- google -->
            <p>
    <div class="g-recaptcha" data-sitekey="这里填写你的SITE密钥"></div><!-- google -->
</p>
            <p>

            <p>
                <label for="remember"><input type="checkbox" name="remember" class="checkbox" value="1" id="remember" /> <?php _e('下次自动登录'); ?></label>
            </p>
        </form>

        <p class="more-link">
            <a href="<?php $options->siteUrl(); ?>"><?php _e('返回首页'); ?></a>
            <?php if($options->allowRegister): ?>
            &bull;
            <a href="<?php $options->registerUrl(); ?>"><?php _e('用户注册'); ?></a>
            <?php endif; ?>
        </p>
    </div>
</div>
<?php
include 'common-js.php';
?>
<script>
$(document).ready(function () {
    $('#name').focus();
});
</script>
<?php
include 'footer.php';
?>

将以下内容覆盖到 /var/Widget/Login.php

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
 * 登录动作
 *
 * @category typecho
 * @package Widget
 * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
 * @license GNU General Public License 2.0
 * @version $Id$
 */

/**
 * 登录组件
 *
 * @category typecho
 * @package Widget
 * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
 * @license GNU General Public License 2.0
 */

/**
 * reCAPTCHA
 *
 * By BLxcwg666
 * https://blog.xcnya.cn
 * [email protected]
 */

class Paul_GCaptcha {
    public static $success, $failed;

    // 发送验证信息
    public static function send($post_data) {
        $postdata = http_build_query($post_data);
        $options = array(
            'http' => array(
                'method' => 'POST',
                'header' => 'Content-type:application/x-www-form-urlencoded',
                'content' => $postdata,
                'timeout' => 15 * 60 // 超时时间
            )
        );
        $context = stream_context_create($options);
        $result = file_get_contents("https://recaptcha.net/recaptcha/api/siteverify", false, $context);
        return $result;
    }

    // 判断验证状况
    public static function check(){
        if($_POST["g-recaptcha-response"]){
            $data = array(
                'secret' => '这里填写你的SECRET密钥',
                'response' => $_POST["g-recaptcha-response"] // 接收用户提交的验证数据
            );

            $result = self::send($data);
            $result = json_decode($result, true);
            $result = $result["success"];

            if($result == true){
                return true; // 验证成功
            }
            else{
                return false; // 验证失败
            }
        }
        else{
            return false; // 用户没有提交到验证信息
        }
    }
}

class Widget_Login extends Widget_Abstract_Users implements Widget_Interface_Do
{
    /**
     * 初始化函数
     *
     * @access public
     * @return void
     */
    public function action()
    {
        // protect
        $this->security->protect();

        /** 如果已经登录 */
        if ($this->user->hasLogin()) {
            /** 直接返回 */
            $this->response->redirect($this->options->index);
        }

        /** 初始化验证类 */
        $validator = new Typecho_Validate();
        $validator->addRule('name', 'required', _t('请输入用户名'));
        $validator->addRule('password', 'required', _t('请输入密码'));

        /** 截获验证异常 */
        if ($error = $validator->run($this->request->from('name', 'password'))) {
            Typecho_Cookie::set('__typecho_remember_name', $this->request->name);

            /** 设置提示信息 */
            $this->widget('Widget_Notice')->set($error);
            $this->response->goBack();
        }

        if(Paul_GCaptcha::check() == true){
            /** 开始验证用户 **/
            $valid = $this->user->login($this->request->name, $this->request->password,
            false, 1 == $this->request->remember ? $this->options->time + $this->options->timezone + 30*24*3600 : 0);

            /** 比对密码 */
            if (!$valid) {
                /** 防止穷举,休眠3秒 */
                sleep(3);

                $this->pluginHandle()->loginFail($this->user, $this->request->name,
                $this->request->password, 1 == $this->request->remember);

                Typecho_Cookie::set('__typecho_remember_name', $this->request->name);
                $this->widget('Widget_Notice')->set(_t('用户名或密码无效'), 'error');
                $this->response->goBack('?referer=' . urlencode($this->request->referer));
            }
            $this->pluginHandle()->loginSucceed($this->user, $this->request->name,
            $this->request->password, 1 == $this->request->remember);

            /** 跳转验证后地址 */
            if (NULL != $this->request->referer) {
                $this->response->redirect($this->request->referer);
            } else if (!$this->user->pass('contributor', true)) {
                /** 不允许普通用户直接跳转后台 */
                $this->response->redirect($this->options->profileUrl);
            } else {
                $this->response->redirect($this->options->adminUrl);
            }
        }else{
            $this->widget('Widget_Notice')->set(_t('验证失败'),'error');
            $this->response->goBack('?referer=' . urlencode($this->request->referer));
        }
    }
}

将上面标注的字段换成自己的 SiteKeySecert Key

注册页面

将以下内容覆盖到 /admin/register.php

<?php
include 'common.php';

if ($user->hasLogin() || !$options->allowRegister) {
    $response->redirect($options->siteUrl);
}
$rememberName = htmlspecialchars(Typecho_Cookie::get('__typecho_remember_name'));
$rememberMail = htmlspecialchars(Typecho_Cookie::get('__typecho_remember_mail'));
Typecho_Cookie::delete('__typecho_remember_name');
Typecho_Cookie::delete('__typecho_remember_mail');

$bodyClass = 'body-100';

include 'header.php';
?>
<div class="typecho-login-wrap">
    <div class="typecho-login">
        <h1><a href="http://typecho.org" class="i-logo">Typecho</a></h1>
        <form action="<?php $options->registerAction(); ?>" method="post" name="register" role="form">
            <p>
                <label for="name" class="sr-only"><?php _e('用户名'); ?></label>
                <input type="text" id="name" name="name" placeholder="<?php _e('用户名'); ?>" value="<?php echo $rememberName; ?>" class="text-l w-100" autofocus />
            </p>
            <p>
                <label for="mail" class="sr-only"><?php _e('Email'); ?></label>
                <input type="email" id="mail" name="mail" placeholder="<?php _e('Email'); ?>" value="<?php echo $rememberMail; ?>" class="text-l w-100" />
            </p>
    <div class="g-recaptcha" data-sitekey="这里填写你的SITE密钥"></div>
</p>
            <p class="submit">
                <button type="submit" class="btn btn-l w-100 primary"><?php _e('注册'); ?></button>
            </p>
            <script src="https://www.recaptcha.net/recaptcha/api.js"></script>
            <p>
        </form>

        <p class="more-link">
            <a href="<?php $options->siteUrl(); ?>"><?php _e('返回首页'); ?></a>
            &bull;
            <a href="<?php $options->adminUrl('login.php'); ?>"><?php _e('用户登录'); ?></a>
        </p>
    </div>
</div>
<?php
include 'common-js.php';
?>
<script>
$(document).ready(function () {
    $('#name').focus();
});
</script>
<?php
include 'footer.php';
?>

将以下内容覆盖到 /var/Widget/Register.php

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
 * 注册组件
 *
 * @author qining
 * @category typecho
 * @package Widget
 */

/**
 * reCAPTCHA
 *
 * By BLxcwg666
 * https://blog.xcnya.cn
 * [email protected]
 */

 class Paul_GCaptcha {
    public static $success, $failed;

    // 发送验证信息
    public static function send($post_data) {
        $postdata = http_build_query($post_data);
        $options = array(
            'http' => array(
                'method' => 'POST',
                'header' => 'Content-type:application/x-www-form-urlencoded',
                'content' => $postdata,
                'timeout' => 15 * 60 // 超时时间
            )
        );
        $context = stream_context_create($options);
        $result = file_get_contents("https://recaptcha.net/recaptcha/api/siteverify", false, $context);
        return $result;
    }

    // 判断验证状况
    public static function check(){
        if($_POST["g-recaptcha-response"]){
            $data = array(
                'secret' => '这里填写你的SECERT密钥',
                'response' => $_POST["g-recaptcha-response"] // 接收用户提交的验证数据
            );

            $result = self::send($data);
            $result = json_decode($result, true);
            $result = $result["success"];

            if($result == true){
                return true; // 验证成功
            }
            else{
                return false; // 验证失败
            }
        }
        else{
            return false; // 用户没有提交到验证信息
        }
    }
}

class Widget_Register extends Widget_Abstract_Users implements Widget_Interface_Do
{
    /**
     * 初始化函数
     *
     * @access public
     * @return void
     */
    public function action()
    {
        // protect
        $this->security->protect();

        /** 如果已经登录 */
        if ($this->user->hasLogin() || !$this->options->allowRegister) {
            /** 直接返回 */
            $this->response->redirect($this->options->index);
        }

        /** 初始化验证类 */
        $validator = new Typecho_Validate();
        $validator->addRule('name', 'required', _t('必须填写用户名称'));
        $validator->addRule('name', 'minLength', _t('用户名至少包含2个字符'), 2);
        $validator->addRule('name', 'maxLength', _t('用户名最多包含32个字符'), 32);
        $validator->addRule('name', 'xssCheck', _t('请不要在用户名中使用特殊字符'));
        $validator->addRule('name', array($this, 'nameExists'), _t('用户名已经存在'));
        $validator->addRule('mail', 'required', _t('必须填写电子邮箱'));
        $validator->addRule('mail', array($this, 'mailExists'), _t('电子邮箱地址已经存在'));
        $validator->addRule('mail', 'email', _t('电子邮箱格式错误'));
        $validator->addRule('mail', 'maxLength', _t('电子邮箱最多包含200个字符'), 200);

        /** 如果请求中有password */
        if (array_key_exists('password', $_REQUEST)) {
            $validator->addRule('password', 'required', _t('必须填写密码'));
            $validator->addRule('password', 'minLength', _t('为了保证账户安全, 请输入至少六位的密码'), 6);
            $validator->addRule('password', 'maxLength', _t('为了便于记忆, 密码长度请不要超过十八位'), 18);
            $validator->addRule('confirm', 'confirm', _t('两次输入的密码不一致'), 'password');
        }

        /** 截获验证异常 */
        if ($error = $validator->run($this->request->from('name', 'password', 'mail', 'confirm'))) {
            Typecho_Cookie::set('__typecho_remember_name', $this->request->name);
            Typecho_Cookie::set('__typecho_remember_mail', $this->request->mail);

            /** 设置提示信息 */
            $this->widget('Widget_Notice')->set($error);
            $this->response->goBack();
        }

        if(Paul_GCaptcha::check() == true){
            $hasher = new PasswordHash(8, true);
        $generatedPassword = Typecho_Common::randString(7);

        $dataStruct = array(
            'name'      =>  $this->request->name,
            'mail'      =>  $this->request->mail,
            'screenName'=>  $this->request->name,
            'password'  =>  $hasher->HashPassword($generatedPassword),
            'created'   =>  $this->options->time,
            'group'     =>  'subscriber'
        );

        $dataStruct = $this->pluginHandle()->register($dataStruct);

        $insertId = $this->insert($dataStruct);
        $this->db->fetchRow($this->select()->where('uid = ?', $insertId)
        ->limit(1), array($this, 'push'));

        $this->pluginHandle()->finishRegister($this);

        $this->user->login($this->request->name, $generatedPassword);

        Typecho_Cookie::delete('__typecho_first_run');
        Typecho_Cookie::delete('__typecho_remember_name');
        Typecho_Cookie::delete('__typecho_remember_mail');

        $this->widget('Widget_Notice')->set(_t('用户 <strong>%s</strong> 已经成功注册, 密码为 <strong>%s</strong>', $this->screenName, $generatedPassword), 'success');
        $this->response->redirect($this->options->adminUrl);
        }else{
            $this->widget('Widget_Notice')->set(_t('验证失败'),'error');
            $this->response->goBack('?referer=' . urlencode($this->request->referer));
        }

    }
}

将上面标注的字段换成自己的 SiteKeySecert Key

完成!打开网站后台,Enjoy!

5

5

我这里加了 dark 标签,如果有需要请参考 reCAPTCHA 文档

后事

提示:如果你和我一样是 Handsome 主题,要实现前台登录,还需要做以下步骤(非 Handsome 主题无需进行这一步)

打开后台,外观设置 → 开发者设置 → 自定义输出 body 尾部的 HTML 代码 中输入以下内容

<script src="https://www.recaptcha.net/recaptcha/api.js" async defer></script>

然后在 /usr/themes/handsome/component/headnav.php 中大约 369 行下面另起一行添加以下内容(Handsome 9.0.2)

<div class="g-recaptcha" style="transform: scale(0.827); -webkit-transform: scale(0.827); transform-origin: 0 0; -webkit-transform-origin: 0 0;" data-sitekey="这里填写你的SITE密钥"></div>

将上面标注的字段换成自己的 SiteKeySecert Key

如果开启了用户注册,则需要在相同文件的大约 390 行下面另起一行同样添加以下内容(Handsome 9.0.2)

<div class="g-recaptcha" style="transform: scale(0.827); -webkit-transform: scale(0.827); transform-origin: 0 0; -webkit-transform-origin: 0 0;" data-sitekey="这里填写你的SITE密钥"></div>

将上面标注的字段换成自己的 SiteKeySecert Key

如果有其他版本行数对不上的并且自己不会修改的小伙伴还请麻烦将 php 文件通过邮箱私发给站长让站长来帮你

大功告成!退出网站登录,刷新首页,Enjoy!

6

6

展望未来

已知问题:Handsome 前台登录的 reCAPTCHA 在深色模式下保持白色,暂时没有解决方案,欢迎各位大佬在评论区提出建议

后记

在后台反复测试几次,一切正常
reCAPTCHA 后台也有了相关记录

至此,reCAPTCHA 在 Typecho 的 部署大功告成!

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...