Skip to content

Commit a09410b

Browse files
author
雾都
committed
安全升级
后台登录采用极验验证
1 parent 0b5e672 commit a09410b

File tree

4 files changed

+147
-85
lines changed

4 files changed

+147
-85
lines changed

admin/login.php

Lines changed: 142 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,71 @@
88

99
initDatabase();
1010

11+
// 从config.php加载极验配置
12+
$config = [];
13+
if (file_exists('../config.php')) {
14+
$config = require '../config.php';
15+
}
16+
17+
// 检查是否启用极验验证
18+
$geetestEnabled = isset($config['geetest']['id'], $config['geetest']['key'], $config['geetest']['api_server']);
19+
20+
// 极验验证码验证函数
21+
function verifyGeetest($lot_number, $captcha_output, $pass_token, $gen_time) {
22+
global $config;
23+
24+
if (!isset($config['geetest'])) {
25+
return true; // 没有配置时跳过验证
26+
}
27+
28+
$query = [
29+
"lot_number" => $lot_number,
30+
"captcha_output" => $captcha_output,
31+
"pass_token" => $pass_token,
32+
"gen_time" => $gen_time,
33+
"sign_token" => hash_hmac('sha256', $lot_number, $config['geetest']['key'])
34+
];
35+
36+
$url = $config['geetest']['api_server'] . "/validate" . "?" . http_build_query($query);
37+
38+
try {
39+
$context = stream_context_create([
40+
'http' => [
41+
'timeout' => 5 // 5秒超时
42+
]
43+
]);
44+
$response = file_get_contents($url, false, $context);
45+
46+
if ($response === false) {
47+
error_log("极验验证请求失败: " . error_get_last()['message']);
48+
return false;
49+
}
50+
51+
$data = json_decode($response, true);
52+
53+
if (!is_array($data) || !isset($data['result'])) {
54+
error_log("极验验证返回无效数据: " . $response);
55+
return false;
56+
}
57+
58+
return $data['result'] === 'success';
59+
} catch (Exception $e) {
60+
error_log("极验验证异常: " . $e->getMessage());
61+
return false;
62+
}
63+
}
64+
1165
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['email'])) {
12-
$adminEmail = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL); // 清理邮箱地址
13-
//echo $adminEmail;
66+
// 如果启用了极验验证,则进行验证
67+
if ($geetestEnabled) {
68+
if (empty($_POST['lot_number']) || empty($_POST['captcha_output']) ||
69+
empty($_POST['pass_token']) || empty($_POST['gen_time'])) {
70+
$loginError = "请完成验证码验证";
71+
goto html;
72+
}
73+
}
74+
75+
$adminEmail = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
1476
// 获取TOTP启用状态
1577
$query = "SELECT totp_enabled FROM admin WHERE email = ?";
1678
$stmt = $pdo->prepare($query);
@@ -39,32 +101,26 @@
39101
$totp = TOTP::create($secret);
40102
// 验证令牌
41103
if ($totp->verify($inputUserToken)) {
42-
$device_id = generateUniqueDeviceId(); // 使用更复杂的设备识别方法
43-
$_SESSION['device_id'] = $device_id; //将自己的设备id存入session
44-
$_SESSION['userToken'] = $secret; // 使用TOTP密钥作为会话标识
45-
$_SESSION['logged_in'] = true; // 标记用户为已登录
104+
$device_id = generateUniqueDeviceId();
105+
$_SESSION['device_id'] = $device_id;
106+
$_SESSION['userToken'] = $secret;
107+
$_SESSION['logged_in'] = true;
46108
$_SESSION['email'] = $adminEmail;
47109
$userToken = $_SESSION['userToken'];
48110

49-
50-
// 确保用户已经登录并且userToken在会话中
51111
if (isset($_SESSION['userToken'], $_SESSION['email'])) {
52112
$userToken = $_SESSION['userToken'];
53113
$email = $_SESSION['email'];
54-
// 检查设备是否已经记录在数据库中
55114
$checkStmt = $pdo->prepare("SELECT id FROM admin_devices
56115
WHERE email = :email AND token = :userToken AND device_id = :device_id");
57116
$checkStmt->execute([':email' => $email, ':userToken' => $userToken, ':device_id' => $device_id]);
58117
$device = $checkStmt->fetch(PDO::FETCH_ASSOC);
59-
$sessionId = session_id(); // 获取当前会话ID
60-
//echo $sessionId;
118+
$sessionId = session_id();
61119

62120
if ($device) {
63-
// 设备已存在,更新最后登录时间
64121
$updateStmt = $pdo->prepare("UPDATE admin_devices SET last_login = CURRENT_TIMESTAMP WHERE id = :id");
65122
$updateStmt->execute([':id' => $device['id']]);
66123
} else {
67-
// 设备不存在,插入新记录
68124
$insertStmt = $pdo->prepare("INSERT INTO admin_devices (email, token, device_id, last_login, ip_address, session_id) VALUES (:email, :userToken, :device_id, CURRENT_TIMESTAMP, :ip_address,:session_id)");
69125
$insertStmt->execute([':email' => $email, ':userToken' => $userToken, ':device_id' => $device_id, ':ip_address' => getRealIp(), ':session_id' => $sessionId]);
70126
}
@@ -91,31 +147,26 @@
91147
$hashedPassword = $stmt->fetchColumn();
92148
}
93149
if (password_verify($inputUserPassword, $hashedPassword)) {
94-
$device_id = generateUniqueDeviceId(); // 使用更复杂的设备识别方法
95-
$_SESSION['device_id'] = $device_id; //将自己的设备id存入session
96-
$_SESSION['userToken'] = $hashedPassword; // 使用哈希后的密码作为会话标识
97-
$_SESSION['logged_in'] = true; // 标记用户为已登录
150+
$device_id = generateUniqueDeviceId();
151+
$_SESSION['device_id'] = $device_id;
152+
$_SESSION['userToken'] = $hashedPassword;
153+
$_SESSION['logged_in'] = true;
98154
$_SESSION['email'] = $adminEmail;
99155
$userToken = $_SESSION['userToken'];
100156

101-
// 确保用户已经登录并且userToken在会话中
102157
if (isset($_SESSION['userToken'], $_SESSION['email'])) {
103158
$userToken = $_SESSION['userToken'];
104159
$email = $_SESSION['email'];
105-
// 检查设备是否已经记录在数据库中
106160
$checkStmt = $pdo->prepare("SELECT id FROM admin_devices
107161
WHERE email = :email AND token = :userToken AND device_id = :device_id");
108162
$checkStmt->execute([':email' => $email, ':userToken' => $userToken, ':device_id' => $device_id]);
109163
$device = $checkStmt->fetch(PDO::FETCH_ASSOC);
110-
$sessionId = session_id(); // 获取当前会话ID
111-
//echo $sessionId;
164+
$sessionId = session_id();
112165

113166
if ($device) {
114-
// 设备已存在,更新最后登录时间
115167
$updateStmt = $pdo->prepare("UPDATE admin_devices SET last_login = CURRENT_TIMESTAMP WHERE id = :id");
116168
$updateStmt->execute([':id' => $device['id']]);
117169
} else {
118-
// 设备不存在,插入新记录
119170
$insertStmt = $pdo->prepare("INSERT INTO admin_devices (email, token, device_id, last_login, ip_address, session_id) VALUES (:email, :userToken, :device_id, CURRENT_TIMESTAMP, :ip_address,:session_id)");
120171
$insertStmt->execute([':email' => $email, ':userToken' => $userToken, ':device_id' => $device_id, ':ip_address' => getRealIp(), ':session_id' => $sessionId]);
121172
}
@@ -154,31 +205,25 @@
154205
$loginError = "用户不存在";
155206
goto html;
156207
}
157-
// 从数据库中获取用户的TOTP密钥
158208
$query = "SELECT totp_secret FROM admin WHERE email = ?";
159209
$stmt = $pdo->prepare($query);
160210
$stmt->execute([$adminEmail]);
161211
$secret = $stmt->fetchColumn();
162-
// 创建TOTP对象
163212
$totp = TOTP::create($secret);
164-
// 验证令牌
165213
if (password_verify($inputUserPassword, $hashedPassword) && $totp->verify($inputUserToken)) {
166-
$device_id = generateUniqueDeviceId(); // 使用更复杂的设备识别方法
167-
$_SESSION['device_id'] = $device_id; //将自己的设备id存入session
168-
$_SESSION['userToken'] = $hashedPassword . '-' . $inputUserToken; // 使用哈希后的密码和totp密钥作为会话标识
169-
$_SESSION['logged_in'] = true; // 标记用户为已登录
214+
$device_id = generateUniqueDeviceId();
215+
$_SESSION['device_id'] = $device_id;
216+
$_SESSION['userToken'] = $hashedPassword . '-' . $inputUserToken;
217+
$_SESSION['logged_in'] = true;
170218
$_SESSION['email'] = $adminEmail;
171219
$userToken = $_SESSION['userToken'];
172220

173-
// 确保用户已经登录并且userToken在会话中
174221
if (isset($_SESSION['userToken'], $_SESSION['email'])) {
175222
$userToken = $_SESSION['userToken'];
176223
$email = $_SESSION['email'];
177-
// 检查设备是否已经记录在数据库中
178224
$checkStmt = $pdo->prepare("SELECT id FROM admin_devices
179225
WHERE email = :email AND token = :userToken AND device_id = :device_id");
180226
$checkStmt->execute([':email' => $email, ':userToken' => $userToken, ':device_id' => $device_id]);
181-
$device = $checkStmt->fetch(PDO::FETCH_ASSOC);
182227
}
183228
} else {
184229
$loginError = "邮箱或密码或TOTP不正确";
@@ -189,7 +234,6 @@
189234
exit;
190235
}
191236
html:
192-
// 显示登录表单
193237
?>
194238
<!DOCTYPE html>
195239
<html lang="en">
@@ -203,7 +247,6 @@
203247
background-color: #f7f7f7;
204248
padding: 20px;
205249
}
206-
207250
.login-form {
208251
background: #fff;
209252
max-width: 300px;
@@ -212,22 +255,19 @@
212255
border-radius: 5px;
213256
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
214257
}
215-
216258
label {
217259
display: block;
218260
margin-bottom: 5px;
219261
}
220-
221262
input[type="text"],
222263
input[type="password"] {
223264
width: 100%;
224265
padding: 10px;
225266
margin-bottom: 20px;
226267
border: 1px solid #ddd;
227268
border-radius: 4px;
228-
box-sizing: border-box; /* makes sure padding doesn't affect width */
269+
box-sizing: border-box;
229270
}
230-
231271
button {
232272
width: 100%;
233273
padding: 10px;
@@ -237,33 +277,91 @@
237277
border-radius: 4px;
238278
cursor: pointer;
239279
}
240-
241280
button:hover {
242281
background-color: #07b9ff;
243282
}
244-
245283
.error {
246284
color: #d9534f;
247285
}
286+
#geetest-captcha {
287+
margin-bottom: 20px;
288+
}
248289
</style>
290+
<?php if ($geetestEnabled): ?>
291+
<script src="https://static.geetest.com/v4/gt4.js"></script>
292+
<?php endif; ?>
249293
</head>
250294
<body>
251295

252-
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" class="login-form">
296+
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" class="login-form" id="login-form">
253297
<label for="email">请输入邮箱:</label>
254298
<input type="text" id="email" name="email" required>
299+
300+
<?php if ($geetestEnabled): ?>
301+
<!-- 极验验证码容器 -->
302+
<div id="geetest-captcha"></div>
303+
<input type="hidden" name="lot_number" id="lotNumber">
304+
<input type="hidden" name="captcha_output" id="captchaOutput">
305+
<input type="hidden" name="pass_token" id="passToken">
306+
<input type="hidden" name="gen_time" id="genTime">
307+
<?php endif; ?>
308+
255309
<p>如果您的账号选择使用TOTP,请输入TOTP密钥并留空密码区域。</p>
256310
<p>使用传统密码,请留空TOTP区域并填写密码。两者都使用请全部填写。</p>
257311
<label for="password">请输入密码:</label>
258312
<input type="password" id="password" name="password">
259313
<label for="totp">输入TOTP密钥:</label>
260314
<input type="password" id="totp" name="totp">
261-
<button type="submit">登录</button>
315+
<button type="submit" id="submit-btn">登录</button>
262316

263317
<?php if (isset($loginError)): ?>
264318
<p class="error"><?php echo $loginError; ?></p>
265319
<?php endif; ?>
266320
</form>
267321

322+
<script>
323+
<?php if ($geetestEnabled): ?>
324+
// 初始化极验验证码
325+
var captcha;
326+
initGeetest4({
327+
captchaId: "<?php echo $config['geetest']['id']; ?>",
328+
product: "bind",
329+
language: "zh-cn"
330+
}, function(instance) {
331+
captcha = instance;
332+
333+
captcha.onReady(function() {
334+
document.getElementById('geetest-captcha').style.display = 'block';
335+
});
336+
337+
captcha.onSuccess(function() {
338+
var result = captcha.getValidate();
339+
document.getElementById('lotNumber').value = result.lot_number;
340+
document.getElementById('captchaOutput').value = result.captcha_output;
341+
document.getElementById('passToken').value = result.pass_token;
342+
document.getElementById('genTime').value = result.gen_time;
343+
document.getElementById('login-form').submit();
344+
});
345+
346+
captcha.appendTo("#geetest-captcha");
347+
});
348+
349+
document.getElementById('login-form').addEventListener('submit', function(e) {
350+
e.preventDefault();
351+
352+
if (!document.getElementById('lotNumber').value) {
353+
captcha.showCaptcha();
354+
} else {
355+
this.submit();
356+
}
357+
});
358+
<?php else: ?>
359+
document.getElementById('login-form').addEventListener('submit', function(e) {
360+
// 没有极验验证时直接提交
361+
return true;
362+
});
363+
<?php endif; ?>
364+
</script>
365+
268366
</body>
269-
</html>
367+
</html>

config.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@
1414
'password' => 'your_mail_pass', // 邮箱密码/授权码
1515
'from' => 'your_mail_address', // 发件人地址
1616
'encryption' => 'ssl' // 加密方式(可选:tls/ssl)
17+
],
18+
'geetest' => [
19+
'id' => '您的极验ID',
20+
'key' => '您的极验KEY',
21+
'api_server' => 'https://gcaptcha4.geetest.com'
1722
]
1823
];

plugins/hook_radom_jump_example/main.php

Lines changed: 0 additions & 20 deletions
This file was deleted.

plugins/template_page_vars_example/main.php

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)