banner
libxcnya.so

libxcnya.so

Nothing...
telegram
twitter
github
email

Typecho 後台添加 Cloudflare Turnstile

前言#

接上集,我們在 Typecho 後台添加了 reCAPTCHA
而這會我們來在 Typecho 後台添加 Cloudflare Turnstile

獲取#

首先打開 Cloudflare,在左邊找到 Turnstile

![1][1]

點進去點這塊的 添加站點

![2][2]

進去以後,站點名稱隨便寫,域寫你的域名,小組件模式選托管

![3][3]

然後把這兩串記住,等會要用

![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>
            <div class="cf-turnstile" data-sitekey="你的站點密鑰" data-callback="javascriptCallback" style="transform: scale(0.926); -webkit-transform: scale(0.926); transform-origin: 0 0; -webkit-transform-origin: 0 0;"></div>
                <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>
            <p>
                <label for="remember">
                    <input<?php if(\Typecho\Cookie::get('__typecho_remember_remember')): ?> checked<?php endif; ?> 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>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></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
 */

/**
 * CF CAPTCHA
 *
 * 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://challenges.cloudflare.com/turnstile/v0/siteverify", false, $context);
        return $result;
    }

    // 判斷驗證狀況
    public static function check(){
        if($_POST["cf-turnstile-response"]){
            $data = array(
                'secret' => '你的密鑰',
                'response' => $_POST["cf-turnstile-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));
        }
    }
}

將上面標註的字段換成自己的 站點密鑰密鑰

註冊頁面#

將以下內容覆蓋到 /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="cf-turnstile" data-sitekey="你的站點密鑰" data-callback="javascriptCallback" style="transform: scale(0.926); -webkit-transform: scale(0.926); transform-origin: 0 0; -webkit-transform-origin: 0 0;"></div>
            <p class="submit">
                <button type="submit" class="btn btn-l w-100 primary"><?php _e('註冊'); ?></button>
            </p>
            <script src="https://challenges.cloudflare.com/turnstile/v0/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
 */

/**
 * CF CAPTCHA
 *
 * 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://challenges.cloudflare.com/turnstile/v0/siteverify", false, $context);
        return $result;
    }

    // 判斷驗證狀況
    public static function check(){
        if($_POST["cf-turnstile-response"]){
            $data = array(
                'secret' => '你的密鑰',
                'response' => $_POST["cf-turnstile-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));
        }

    }
}

將上面標註的字段換成自己的 站點密鑰密鑰

完成!打開網站後台,Enjoy!

![5][5]

這裡的顏色是根據設備主題的,如果你需要固定顏色,請參考 [Turnstile 文檔][6] 來自行添加 theme 標籤

後事#

提示:如果你和我一樣是 Handsome 主題,要實現前台登錄,還需要做以下步驟(非 Handsome 主題無需進行這一步):::

打開後台,外觀設置 → 開發者設置 → 自定義輸出 body 尾部的 HTML 代碼 中輸入以下內容

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

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

<div class="cf-turnstile" style="transform: scale(0.827); -webkit-transform: scale(0.827); transform-origin: 0 0; -webkit-transform-origin: 0 0;" data-sitekey="你的站點密鑰"></div>

將上面標註的字段換成自己的 站點密鑰密鑰

如果開啟了用戶註冊,則需要在相同文件的大約 390 行下面另起一行同樣添加以下內容(Handsome 9.0.2)

<div class="cf-turnstile" style="transform: scale(0.827); -webkit-transform: scale(0.827); transform-origin: 0 0; -webkit-transform-origin: 0 0;" data-sitekey="你的密鑰"></div>

將上面標註的字段換成自己的 站點密鑰密鑰

如果有其他版本行數對不上的並且自己不會修改的小夥伴還請麻煩將 php 文件通過郵箱私發給站長讓站長來幫你

大功告成!退出網站登錄,刷新首頁,Enjoy!

6

展望未來#

已知問題:Handsome 前台登錄的 Turnstile 在深色模式下保持白色,暫時沒有解決方案,歡迎各位大佬在評論區提出建議
咕咕咕:Cf 的這個驗證是支持 IP 鑑權的,理論上寫上更安全,但是文檔裡面示例不夠全面,如果有大神歡迎在評論區適配這個特性

此文由 Mix Space 同步更新至 xLog
原始鏈接為 https://blog.nekorua.com/posts/coding/30.html


載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。