为 Typecho 的登陆加上验证码

发布于 2020-01-11  430 次阅读


众所周知,typecho 的登陆界面是没有验证码选项的,虽然官方在验证中增加了防暴力破解,但是我依然感觉不太安全(好像用验证码也没有安全到哪里去。。 emmm 总比没有好)先来看下官方的登陆图

再看下更改后的

emmmm 高级了不少的样子


方法

此方法仅在 1.1 版测试过

首先将以下代码保存为 captcha.php 并放入/admin/中

<?php
//必须至于顶部, 多服务器端记录验证码信息,便于用户输入后做校验
    session_start();

    //默认返回的是黑色的照片
    $image = imagecreatetruecolor(80, 38);
    //将背景设置为白色的
    $bgcolor = imagecolorallocate($image, 255, 255, 255);
    //将白色铺满地图
    imagefill($image, 0, 0, $bgcolor);

    //空字符串,每循环一次,追加到字符串后面  
    $captch_code='';

//验证码为随机四个字符,数字和字母
   for ($i=0; $i <4 ; $i++) { 
        $fontsize=8;
    $fontcolor=imagecolorallocate($image,rand(50,150),rand(50,150),rand(50,150));
        //字典
        $data='abcdefghijkLmnpqrstuvwxyz123456789';
        $fontcontent = substr($data,rand(0,strlen($data)) ,1);
        $captch_code.= $fontcontent;

        $x=($i*80/4)+rand(5,10);
        $y=rand(5,20);

        imagestring($image,$fontsize,$x,$y,$fontcontent,$fontcolor);
    }

   $_SESSION['verify'] = $captch_code;
//为验证码增加干扰元素,控制好颜色,
//点
    for ($i=0; $i < 300; $i++) { 
        $pointcolor = imagecolorallocate($image,rand(50,200),rand(50,200),rand(50,200));
        imagesetpixel($image, rand(1,99), rand(1,37), $pointcolor);
    }

//为验证码增加干扰元素
//线
    for ($i=0; $i < 3; $i++) { 
        $linecolor = imagecolorallocate($image,rand(10,220),rand(10,220),rand(50,220));
        imageline($image, rand(1,99), rand(1,37),rand(1,99), rand(1,37) ,$linecolor);
    }

    header('content-type:image/png');
    imagepng($image);

    //销毁
    imagedestroy($image);

以上用来生成图片验证码


再将修改过的 login.php 放入/admin/中

<?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';
?>
<style>
    .inline {
        vertical-align: middle;
    }
</style>
<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>
                <label for="name" class="sr-only"><?php _e(' 验证码'); ?></label>
                <input type="text" id="verify" name="verify" placeholder="<?php _e(' 验证码'); ?>" class="text-l w-75 inline" />
                <img id="captcha" src="./captcha.php?r=<?php echo rand(); ?>" onclick="document.getElementById('captcha').src='./captcha.php?r='+Math.random()" class="inline">
            </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>
            <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): ?>
            •
            <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';
?>

此处新增的代码如下

<style>
    .inline {
        vertical-align: middle;
    }
</style>

用来控制样式,使得验证码输入框与验证码图片处于一条 line 上

<p>
    <label for="name" class="sr-only"><?php _e(' 验证码'); ?></label>
    <input type="text" id="verify" name="verify" placeholder="<?php _e(' 验证码'); ?>" class="text-l w-75 inline" />
    <img id="captcha" src="./captcha.php?r=<?php echo rand(); ?>" onclick="document.getElementById('captcha').src='./captcha.php?r='+Math.random()" class="inline">
</p>

输入框,点击图片可更换验证码


最后修改/var/Widget/login.php(验证文件)

<?php
session_start();
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
 */
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(' 请输入密码'));
        $validator->addRule('verify', 'required', _t(' 请输入验证码'));

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

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

        if ($_POST['verify'] != $_SESSION['verify']) {
            /** 防止穷举, 休眠 3 秒 */
            sleep(3);
            $this->widget('Widget_Notice')->set(_t(' 验证码错误!'), 'error');
            $this->response->goBack('?referer=' . urlencode($this->request->referer));
        }else if (($_POST['verify'] == $_SESSION['verify'])){
            /** 开始验证用户 **/
            $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);
        }
    }
}

启动 session

session_start();

增加验证码未输入提醒

$validator->addRule('verify', 'required', _t(' 请输入验证码'));
$error = $validator->run($this->request->from('name', 'password', 'verify'))

增加验证码校验

 if ($_POST['verify'] != $_SESSION['verify']) {
            /** 防止穷举, 休眠 3 秒 */
            sleep(3);
            $this->widget('Widget_Notice')->set(_t(' 验证码错误!'), 'error');
            $this->response->goBack('?referer=' . urlencode($this->request->referer));
        }else if (($_POST['verify'] == $_SESSION['verify'])){
}