feat(auth): 新增谷歌验证码登录 implement Google Authenticator support for two-factor authentication

- Add Google Authenticator integration for enhanced login security-Update admin edit page to include login type selection
- Modify login process to support two-factor authentication
- Add new database fields for login type and GA secret
- Update client-side JavaScript to handle GA code input and validation
This commit is contained in:
wolfcode
2024-11-12 10:51:48 +08:00
parent f3e5a041d1
commit 75c668b966
10 changed files with 136 additions and 9 deletions

View File

@@ -64,15 +64,20 @@ class Index extends AdminController
$rule = [];
$this->validate($post, $rule);
try {
$save = $row
->allowField(['head_img', 'phone', 'remark', 'update_time'])
->save($post);
}catch (Exception $e) {
$login_type = $post['login_type'] ?? 1;
if ($login_type == 2) {
$ga_secret = (new SystemAdmin())->where('id', $id)->value('ga_secret');
if (empty($ga_secret)) $this->error('请先绑定谷歌验证器');
}
$save = $row->allowField(['head_img', 'phone', 'remark', 'update_time', 'login_type'])->save($post);
}catch (\PDOException $e) {
$this->error('保存失败');
}
$save ? $this->success('保存成功') : $this->error('保存失败');
}
$this->assign('row', $row);
$notes = (new SystemAdmin())->notes;
$this->assign('notes', $notes);
return $this->fetch();
}
@@ -80,9 +85,6 @@ class Index extends AdminController
* 修改密码
* @param Request $request
* @return string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function editPassword(Request $request): string
{
@@ -122,4 +124,37 @@ class Index extends AdminController
return $this->fetch();
}
/**
* 设置谷歌验证码
* @param Request $request
* @return string
* @throws Exception
*/
public function set2fa(Request $request): string
{
$id = $this->adminUid;
$row = (new SystemAdmin())->withoutField('password')->find($id);
if (!$row) $this->error('用户信息不存在');
// You can see: https://gitee.com/wolf-code/authenticator
$ga = new \Wolfcode\Authenticator\google\PHPGangstaGoogleAuthenticator();
if (!$request->isAjax()) {
$old_secret = $row->ga_secret;
$secret = $ga->createSecret(32);
$ga_title = $this->isDemo ? 'EasyAdmin8演示环境' : '可自定义修改显示标题';
$dataUri = $ga->getQRCode($ga_title, $secret)->getDataUri();
$this->assign(compact('row', 'dataUri', 'old_secret', 'secret'));
return $this->fetch();
}
$this->isDemo && $this->error('演示环境下不允许修改');
$post = $request->post();
$ga_secret = $post['ga_secret'] ?? '';
$ga_code = $post['ga_code'] ?? '';
if (empty($ga_code)) $this->error('请输入验证码');
if (!$ga->verifyCode($ga_secret, $ga_code)) $this->error('验证码错误');
$row->ga_secret = $ga_secret;
$row->login_type = 2;
$row->save();
$this->success('操作成功');
}
}

View File

@@ -56,6 +56,11 @@ class Login extends AdminController
if ($admin->status == 0) {
$this->error('账号已被禁用');
}
if ($admin->login_type == 2) {
if (empty($post['ga_code'])) $this->error('请输入谷歌验证码', ['is_ga_code' => true]);
$ga = new \Wolfcode\Authenticator\google\PHPGangstaGoogleAuthenticator();
if (!$ga->verifyCode($admin->ga_secret, $post['ga_code'])) $this->error('谷歌验证码错误');;
}
$admin->login_num += 1;
$admin->save();
$admin = $admin->toArray();

View File

@@ -10,6 +10,13 @@ class SystemAdmin extends TimeModel
protected $deleteTime = 'delete_time';
public array $notes = [
'login_type' => [
1 => '密码登录',
2 => '密码 + 谷歌验证码登录'
],
];
public function getAuthList()
{
$list = (new SystemAuth())

View File

@@ -30,6 +30,15 @@
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">登录方式</label>
<div class="layui-input-block">
{foreach notes.login_type as $key=>$val}
<input type="radio" name="login_type" lay-skin="primary" title="{$val}" value="{$key}" lay-filter="loginType-filter" {if $key==$row.login_type}checked=""{/if}>
{/foreach}
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注信息</label>
<div class="layui-input-block">

View File

@@ -0,0 +1,45 @@
<div class="layuimini-container">
<form id="app-form" class="layui-form layuimini-form" autocomplete="off">
{if $old_secret}
<div class="layui-card">
<div class="layui-card-header">提示</div>
<div class="layui-card-body">
当前账号已经绑定过了 谷歌验证码 ,如果重新保存将替换
</div>
</div>
{/if}
<div class="layui-form-item">
<label class="layui-form-label required">验证秘钥</label>
<div class="layui-input-block">
<input type="text" name="ga_secret" class="layui-input" value="{$secret}" readonly disabled>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">二维码</label>
<div class="layui-input-block">
<img src="{$dataUri}" alt="二维码" style="width: 200px;height: 200px">
<div class="layui-text layui-font-cyan layui-font-12">
使用&nbsp;
<a href="https://2fas.com" target="_blank"><span class="layui-text layui-font-blue">2FAS</span></a>
&nbsp;或者&nbsp;
<a href="https://cn.bing.com/search?q=Google+Authenticator" target="_blank"><span class="layui-text layui-font-blue">Google Authenticator</span></a>
&nbsp;APP 扫描二维码 输入验证码 进行绑定
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">谷歌验证码</label>
<div class="layui-input-block">
<input type="text" name="ga_code" class="layui-input" maxlength="6" lay-verify="required" placeholder="扫描二维码,输入验证码" value="">
</div>
</div>
<div class=" hr-line">
</div>
<div class="layui-form-item text-center">
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
</div>
</form>
</div>

View File

@@ -22,6 +22,11 @@
<span class="bind-password icon icon-4"></span>
</div>
<div class="item layui-hide" id="gaCode">
<span class="icon icon-3"></span>
<input type="text" name="ga_code" placeholder="谷歌验证码" maxlength="6">
</div>
{if $captcha == 1}
<div id="validatePanel" class="item" style="width: 137px;">
<input type="text" name="captcha" placeholder="请输入验证码" maxlength="4">

View File

@@ -37,7 +37,8 @@
"qiniu/php-sdk": "v7.11.0",
"ext-mysqli": "*",
"ext-pdo": "*",
"wolf-leo/phplogviewer": "^0.11.1"
"wolf-leo/phplogviewer": "^0.11.3",
"wolfcode/authenticator": "^0.0.3"
},
"require-dev": {
"symfony/var-dumper": ">=4.2",

View File

@@ -97,6 +97,8 @@ CREATE TABLE `ea_system_admin`
`create_time` int(11) DEFAULT NULL COMMENT '创建时间',
`update_time` int(11) DEFAULT NULL COMMENT '更新时间',
`delete_time` int(11) DEFAULT NULL COMMENT '删除时间',
`login_type` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '登录方式',
`ga_secret` varchar(32) NOT NULL DEFAULT '' COMMENT '谷歌验证码秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) USING BTREE,
KEY `phone` (`phone`)

View File

@@ -164,10 +164,22 @@ define(["jquery", "easy-admin", "echarts", "echarts-theme", "miniAdmin", "miniTa
})
},
editAdmin: function () {
let form = layui.form
form.on('radio(loginType-filter)', function (data) {
let elem = data.elem
let value = elem.value
if (value === '2') {
let width = screen.width < 768 ? '85%' : '60%'
ea.open('绑定谷歌验证码', ea.url('index/set2fa'), width, '75%')
}
});
ea.listen();
},
editPassword: function () {
ea.listen();
}
},
set2fa: function () {
ea.listen();
},
};
});

View File

@@ -39,6 +39,12 @@ define(["jquery", "easy-admin"], function ($, ea) {
window.location = ea.url('index');
})
}, function (res) {
let data = res.data
if (data.is_ga_code) {
let elem = $('#gaCode')
elem.removeClass('layui-hide');
elem.find('input').focus()
}
ea.msg.error(res.msg, function () {
$('#refreshCaptcha').trigger("click");
});