You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

507 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
require_once("config.php"); // 数据库连接配置
session_start();
// 反爬机制配置
$antiSpamConfig = [
'login_attempts_limit' => 5, // 登录尝试次数限制
'block_time' => 15 * 60, // 封锁时间(秒)
'check_interval' => 60 * 60, // 检查时间窗口(秒)
'ip_blacklist_file' => __DIR__ . '/data/ip_blacklist.txt',
'login_attempts_file' => __DIR__ . '/data/login_attempts.json'
];
$antiSpamDir = dirname($antiSpamConfig['login_attempts_file']);
if (!file_exists($antiSpamDir)) {
mkdir($antiSpamDir, 0777, true);
}
// 获取客户端IP地址
function getClientIP() {
if (isset($_SERVER['HTTP_CLIENT_IP']))
return $_SERVER['HTTP_CLIENT_IP'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
return explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
return $_SERVER['REMOTE_ADDR'];
}
// 检查IP是否在黑名单中
function isIPBlacklisted($ip, $blacklistFile) {
if (!file_exists($blacklistFile)) return false;
$blacklist = file($blacklistFile, FILE_IGNORE_NEW_LINES);
return in_array($ip, $blacklist);
}
// 记录登录尝试
function logLoginAttempt($ip, $success, $config) {
$attempts = [];
$file = $config['login_attempts_file'];
// 读取现有记录
if (file_exists($file)) {
$json = file_get_contents($file);
$attempts = json_decode($json, true) ?: [];
}
// 添加新记录
$attempts[] = [
'ip' => $ip,
'time' => time(),
'success' => $success
];
// 只保留指定时间窗口内的记录
$threshold = time() - $config['check_interval'];
$attempts = array_filter($attempts, function($a) use ($threshold) {
return $a['time'] >= $threshold;
});
// 保存记录
file_put_contents($file, json_encode(array_values($attempts)));
// 如果失败次数超过限制将IP加入黑名单
$ipAttempts = array_filter($attempts, function($a) use ($ip) {
return $a['ip'] === $ip && !$a['success'];
});
if (count($ipAttempts) >= $config['login_attempts_limit']) {
addToIPBlacklist($ip, $config['ip_blacklist_file']);
return true; // IP已被封锁
}
return false;
}
// 添加IP到黑名单
function addToIPBlacklist($ip, $blacklistFile) {
file_put_contents($blacklistFile, $ip . PHP_EOL, FILE_APPEND);
}
// 检查并处理验证码请求
if (isset($_GET['get_captcha'])) {
// 可以添加IP请求频率检查
header('Content-Type: image/png');
include('verify.php');
exit;
}
// 获取客户端IP
$clientIP = getClientIP();
// 检查IP是否被封锁
if (isIPBlacklisted($clientIP, $antiSpamConfig['ip_blacklist_file'])) {
http_response_code(403);
die("您的IP已被封锁请稍后再试。");
}
// 密码哈希加密配置(可选:设置算法参数)
// define('PASSWORD_COST', 12); // 默认10数值越大越安全但性能消耗越高
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = trim($_POST["username"]); // 去除首尾空格
$plainPassword = trim($_POST["pwd"]); // 原始密码(不存储)
$code = trim($_POST["code"]);
// 输入验证:非空检查
if (empty($username) || empty($plainPassword) || empty($code)) {
logLoginAttempt($clientIP, false, $antiSpamConfig);
echo "<script>alert('用户名、密码或验证码不可为空!'); location.href='login.php';</script>";
exit();
}
// 验证码验证区分大小写需调整strtolower
if (strtolower($_SESSION["code"]) !== strtolower($code)) {
logLoginAttempt($clientIP, false, $antiSpamConfig);
echo "<script>alert('验证码错误!请重新登录!'); location.href='login.php';</script>";
exit();
}
// 数据库查询:获取用户哈希密码
$stmt = $connect->prepare("SELECT id, username, password FROM librarian WHERE username=?");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
if ($user = $result->fetch_assoc()) {
// 验证密码哈希
if (password_verify($plainPassword, $user["password"])) {
// 密码正确,更新会话并跳转
logLoginAttempt($clientIP, true, $antiSpamConfig);
$_SESSION["user_id"] = $user["id"];
$_SESSION["username"] = $user["username"];
$_SESSION["login_time"] = time(); // 记录登录时间(可选)
// 自动更新哈希版本(可选:若使用旧算法)
// if (password_needs_rehash($user["password"], PASSWORD_DEFAULT, ['cost' => PASSWORD_COST])) {
// $newHashedPassword = password_hash($plainPassword, PASSWORD_DEFAULT, ['cost' => PASSWORD_COST]);
// $updateStmt = $connect->prepare("UPDATE librarian SET password=? WHERE id=?");
// $updateStmt->bind_param("si", $newHashedPassword, $user["id"]);
// $updateStmt->execute();
// $updateStmt->close();
// }
header("Location: admin_index.php");
exit();
} else {
// 密码错误
logLoginAttempt($clientIP, false, $antiSpamConfig);
$stmt->close();
echo "<script>alert('用户名或密码错误!请重新登录!'); location.href='login.php';</script>";
exit();
}
} else {
// 用户不存在
logLoginAttempt($clientIP, false, $antiSpamConfig);
$stmt->close();
echo "<script>alert('用户名或密码错误!请重新登录!'); location.href='login.php';</script>";
exit();
}
$stmt->close();
}
// 退出登录逻辑
if (isset($_GET['tj']) && $_GET['tj'] == 'out') {
session_destroy();
header('location: login.php');
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="shortcut icon" href="icon/favicon.ico" type="image/x-icon">
<title>图书后台管理系统登录功能</title>
<style>
.bg-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
z-index: -1;
}
.bg-item {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity 1s ease-in-out;
}
.bg-item.active {
opacity: 1;
}
body * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-user-drag: none;
-moz-user-drag: none;
-ms-user-drag: none;
user-drag: none;
}
input, textarea {
-webkit-user-select: auto !important;
-moz-user-select: auto !important;
-ms-user-select: auto !important;
user-select: auto !important;
}
.copyright {
position: fixed;
bottom: 15px;
left: 50%;
transform: translateX(-50%);
color: rgba(255,255,255,0.6);
font-size: 14px;
z-index: 100;
}
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
@keyframes slideInRight { from { opacity: 0; transform: translateX(50px); } to { opacity: 1; transform: translateX(0); } }
@keyframes shake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } }
body {
margin: 0;
padding: 0;
font-family: 'Microsoft YaHei', sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
min-height: 100vh;
}
.big_box {
width: 960px;
height: 500px;
margin: auto;
background: rgba(255, 255, 255, 0.38);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
overflow: hidden;
border-radius: 20px 0 0 20px;
backdrop-filter: blur(5px);
display: flex;
position: relative;
z-index: 10;
}
.left_box {
width: 50%;
height: 100%;
overflow: hidden;
clip-path: ellipse(100% 100% at 0% 50%);
}
.left_box img {
width: 100%;
height: 100%;
object-fit: cover;
}
.right_box {
width: 50%;
text-align: center;
animation: slideInRight 0.8s ease-out;
display: flex;
flex-direction: column;
justify-content: center;
padding: 40px;
box-sizing: border-box;
background: transparent;
}
table {
margin: auto;
animation: fadeIn 1s ease-out;
width: 100%;
max-width: 320px;
}
h2 {
margin-top: 0;
color: #333;
animation: fadeIn 0.8s ease-out;
margin-bottom: 30px;
}
.iput {
width: 100%;
height: 40px;
border: 1px solid #ddd;
border-radius: 6px;
padding: 0 15px;
font-size: 14px;
background-color: rgba(255, 255, 255, 0.9);
}
.iput:focus {
border-color: #4a90e2;
box-shadow: 0 0 8px rgba(74, 144, 226, 0.2);
outline: none;
}
.iput1, .iut2 {
width: 120px;
height: 45px;
font-size: 15px;
margin: 0 10px;
cursor: pointer;
transition: all 0.3s;
}
.iput1 {
background-color: #4a90e2;
color: white;
border: none;
border-radius: 6px;
}
.iput1:hover {
background-color: #3a7bc8;
}
.iut2 {
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 6px;
}
.iut2:hover {
background-color: #e0e0e0;
}
.big_box.shake {
animation: shake 0.3s ease-in-out forwards;
}
tr {
height: 60px;
}
.verify-container {
position: relative;
}
.verify-code {
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
height: 30px;
cursor: pointer;
border-radius: 4px;
}
.core-value {
position: absolute;
pointer-events: none;
font-size: 18px;
font-family: '微软雅黑';
color: #FF0000;
user-select: none;
z-index: 999;
transform: scale(0.1);
transition: transform 0.3s ease-out;
}
.core-value.active {
transform: scale(1);
}
</style>
</head>
<body oncontextmenu="return false;" ondragstart="return false;" onselectstart="return false;">
<div class="bg-container" id="bgContainer">
<img src="image/2.png" alt="背景1" class="bg-item">
<img src="image/4.png" alt="背景2" class="bg-item active">
<img src="image/5.png" alt="背景3" class="bg-item">
<img src="image/1.jpg" alt="背景4" class="bg-item">
</div>
<div class="big_box">
<div class="left_box"><img src="image/ysjf.jpg" draggable="false"></div>
<div class="right_box">
<h2>图书管理后台管理中心</h2>
<form method="post" action="" onsubmit="return check()">
<table>
<tr>
<td>
<input type="text" name="username" class="iput" placeholder="请输入用户名">
</td>
</tr>
<tr>
<td>
<input type="password" name="pwd" class="iput" placeholder="请输入密码">
</td>
</tr>
<tr>
<td>
<div class="verify-container">
<input type="text" name="code" class="iput" placeholder="请输入验证码">
<img src="verify.php" alt="验证码" class="verify-code" onclick="this.src='verify.php?'+Math.random()" draggable="false">
</div>
</td>
</tr>
<tr>
<td align="center">
<input type="submit" name="login" value="登录" class="iput1">
<input type="button" name="register" value="注册" class="iut2" onclick="window.location.href='register.php'">
</td>
</tr>
</table>
</form>
</div>
</div>
<div class="copyright">
@2025-05.22 由唯出品/gitee/github/4230603094
</div>
<script>
const bgImages = document.querySelectorAll('.bg-item');
let currentIndex = 0;
function switchBackground() {
bgImages[currentIndex].classList.remove('active');
currentIndex = (currentIndex + 1) % bgImages.length;
bgImages[currentIndex].classList.add('active');
}
document.addEventListener('DOMContentLoaded', function() {
setInterval(switchBackground, 5000);
// 完全禁止右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
// 禁止选择文本
document.addEventListener('selectstart', function(e) {
e.preventDefault();
});
// 禁止拖拽
document.addEventListener('dragstart', function(e) {
e.preventDefault();
});
// 输入框聚焦效果
const inputs = document.querySelectorAll('.iput');
inputs.forEach(input => {
input.addEventListener('focus', function() {
this.style.boxShadow = '0 0 8px rgba(74, 144, 226, 0.3)';
});
input.addEventListener('blur', function() {
this.style.boxShadow = '';
});
});
// 确保所有图片不可拖拽
document.querySelectorAll('img').forEach(img => {
img.draggable = false;
});
});
function check() {
const username = document.querySelector('input[name="username"]').value;
const password = document.querySelector('input[name="pwd"]').value;
const code = document.querySelector('input[name="code"]').value;
if (!username || !password || !code) {
alert('用户名、密码和验证码不可为空!');
return false;
}
return true;
}
const coreValues = [
'富强', '民主', '文明', '和谐',
'自由', '平等', '公正', '法治',
'爱国', '敬业', '诚信', '友善'
].flatMap(word => [word, word]);
document.addEventListener('click', (e) => {
if (e.button !== 0) return;
const x = e.clientX;
const y = e.clientY;
const randomIndex = Math.floor(Math.random() * coreValues.length);
const valueText = coreValues[randomIndex];
const span = document.createElement('span');
span.className = 'core-value';
span.textContent = valueText;
span.style.left = `${x}px`;
span.style.top = `${y}px`;
span.classList.add('active');
document.body.appendChild(span);
setTimeout(() => {
span.remove();
}, 1000);
});
</script>
</body>
</html>