Typecho 后台添加 Cloudflare Turnstile
前言
接上集,我们在 Typecho 后台添加了 reCAPTCHA
而这会我们来在 Typecho 后台添加 Cloudflare Turnstile
获取
首先打开 Cloudflare,在左边找到 Turnstile
点进去点这块的 添加站点
进去以后,站点名称随便写,域写你的域名,小组件模式选托管
然后把这两串记住,等会要用
安装
改的东西挺多的,理论上把 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): ?>
•
<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>
•
<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!
这里的颜色是根据设备主题的,如果你需要固定颜色,请参考 Turnstile 文档 来自行添加 theme 标签
后事
打开后台,外观设置 → 开发者设置 → 自定义输出 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!
展望未来
已知问题:Handsome 前台登录的 Turnstile 在深色模式下保持白色,暂时没有解决方案,欢迎各位大佬在评论区提出建议
咕咕咕:Cf 的这个验证是支持 IP 鉴权的,理论上写上更安全,但是文档里面示例不够全面,如果有大神欢迎在评论区适配这个特性