88
99initDatabase ();
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+
1165if ($ _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 );
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 }
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 }
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不正确 " ;
189234 exit ;
190235}
191236html:
192- // 显示登录表单
193237?>
194238<!DOCTYPE html>
195239<html lang="en">
203247 background-color: #f7f7f7;
204248 padding: 20px;
205249 }
206-
207250 .login-form {
208251 background: #fff;
209252 max-width: 300px;
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;
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>
0 commit comments