您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# 如何使用PHP实现用户登录
## 前言
用户登录系统是Web开发中最基础也最重要的功能之一。无论是电商网站、社交平台还是内容管理系统,几乎所有的Web应用都需要用户认证机制。本文将详细介绍如何使用PHP实现一个完整的用户登录系统,涵盖从数据库设计到前端交互的全过程。
## 目录
1. [系统需求分析](#系统需求分析)
2. [数据库设计与实现](#数据库设计与实现)
3. [前端登录表单设计](#前端登录表单设计)
4. [PHP登录逻辑实现](#php登录逻辑实现)
5. [会话管理与安全性](#会话管理与安全性)
6. [密码加密与验证](#密码加密与验证)
7. [记住我功能实现](#记住我功能实现)
8. [登录限制与防护](#登录限制与防护)
9. [错误处理与用户体验](#错误处理与用户体验)
10. [完整代码示例](#完整代码示例)
11. [总结与扩展](#总结与扩展)
---
## 系统需求分析
在开始编码之前,我们需要明确登录系统的基本需求:
1. **用户凭证存储**:需要存储用户名/邮箱和密码
2. **身份验证**:验证用户提交的凭证是否正确
3. **会话管理**:登录后保持用户状态
4. **安全性**:
- 密码加密存储
- 防止SQL注入
- CSRF防护
- 暴力破解防护
5. **用户体验**:
- 友好的错误提示
- "记住我"功能
- 密码找回功能(本文不涉及)
---
## 数据库设计与实现
### 用户表结构设计
```sql
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`email` varchar(100) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_login` datetime DEFAULT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
创建config/database.php文件:
<?php
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', 'password');
define('DB_NAME', 'auth_demo');
function getDBConnection() {
try {
$dsn = "mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
return new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
<!-- login.php -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户登录</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="card shadow">
<div class="card-body">
<h3 class="card-title text-center mb-4">用户登录</h3>
<form id="loginForm" action="authenticate.php" method="POST">
<!-- CSRF令牌 -->
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="mb-3">
<label for="username" class="form-label">用户名/邮箱</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">记住我</label>
</div>
<button type="submit" class="btn btn-primary w-100">登录</button>
</form>
<div class="mt-3 text-center">
<a href="forgot_password.php">忘记密码?</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
创建authenticate.php:
<?php
session_start();
require_once 'config/database.php';
require_once 'includes/functions.php';
// 验证CSRF令牌
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("CSRF token validation failed");
}
// 获取用户输入
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
// 基本验证
if (empty($username) || empty($password)) {
redirectWithError('用户名和密码不能为空');
}
// 数据库查询
try {
$db = getDBConnection();
$stmt = $db->prepare("SELECT * FROM users WHERE username = :username OR email = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
if (!$user) {
logFailedAttempt($username);
redirectWithError('用户名或密码错误');
}
// 验证密码
if (!password_verify($password, $user['password_hash'])) {
logFailedAttempt($username);
redirectWithError('用户名或密码错误');
}
// 检查账户是否激活
if (!$user['is_active']) {
redirectWithError('您的账户已被禁用,请联系管理员');
}
// 登录成功处理
handleSuccessfulLogin($user, $remember);
} catch (PDOException $e) {
error_log("Login error: " . $e->getMessage());
redirectWithError('系统错误,请稍后再试');
}
// 重定向到受保护页面
header("Location: dashboard.php");
exit;
创建includes/session.php:
<?php
// 安全会话配置
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // 仅在HTTPS下使用
ini_set('session.use_strict_mode', 1);
// 自定义会话名称
session_name('AUTHSESSID');
// 启动会话
session_start();
// 定期更换会话ID
if (!isset($_SESSION['created'])) {
$_SESSION['created'] = time();
} elseif (time() - $_SESSION['created'] > 1800) {
session_regenerate_id(true);
$_SESSION['created'] = time();
}
// 检查用户是否已登录
function isUserLoggedIn() {
return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
}
// 获取当前用户ID
function getCurrentUserId() {
return $_SESSION['user_id'] ?? null;
}
// 生成CSRF令牌
function generateCSRFToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// includes/auth_functions.php
/**
* 创建密码哈希
*/
function createPasswordHash($password) {
return password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
}
/**
* 验证密码
*/
function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
/**
* 检查密码是否需要重新哈希
*/
function needsRehash($hash) {
return password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 12]);
}
/**
* 生成随机密码
*/
function generateRandomPassword($length = 12) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()';
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, strlen($chars) - 1)];
}
return $password;
}
remember_tokens表:CREATE TABLE `remember_tokens` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`token` varchar(64) NOT NULL,
`expires_at` datetime NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `token` (`token`),
KEY `user_id` (`user_id`),
CONSTRNT `remember_tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
// includes/remember_me.php
function setRememberMeCookie($userId) {
$token = bin2hex(random_bytes(32));
$expires = time() + 60 * 60 * 24 * 30; // 30天
// 存储到数据库
$db = getDBConnection();
$stmt = $db->prepare("INSERT INTO remember_tokens (user_id, token, expires_at) VALUES (?, ?, ?)");
$stmt->execute([$userId, $token, date('Y-m-d H:i:s', $expires)]);
// 设置cookie
setcookie(
'remember_me',
$token,
$expires,
'/',
'',
true, // 仅HTTPS
true // HttpOnly
);
}
function checkRememberMeToken() {
if (isset($_COOKIE['remember_me']) && !isUserLoggedIn()) {
$token = $_COOKIE['remember_me'];
$db = getDBConnection();
$stmt = $db->prepare("SELECT user_id FROM remember_tokens WHERE token = ? AND expires_at > NOW()");
$stmt->execute([$token]);
$result = $stmt->fetch();
if ($result) {
$userId = $result['user_id'];
$_SESSION['user_id'] = $userId;
// 更新令牌有效期
$newExpires = time() + 60 * 60 * 24 * 30;
$stmt = $db->prepare("UPDATE remember_tokens SET expires_at = ? WHERE token = ?");
$stmt->execute([date('Y-m-d H:i:s', $newExpires), $token]);
setcookie(
'remember_me',
$token,
$newExpires,
'/',
'',
true,
true
);
}
}
}
login_attempts表:CREATE TABLE `login_attempts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip_address` varchar(45) NOT NULL,
`username` varchar(255) NOT NULL,
`attempt_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `ip_address` (`ip_address`),
KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
// includes/security.php
function logFailedAttempt($username) {
$ip = $_SERVER['REMOTE_ADDR'];
$db = getDBConnection();
$stmt = $db->prepare("INSERT INTO login_attempts (ip_address, username) VALUES (?, ?)");
$stmt->execute([$ip, $username]);
}
function isLoginBlocked($username) {
$ip = $_SERVER['REMOTE_ADDR'];
$db = getDBConnection();
// 检查IP限制
$stmt = $db->prepare("SELECT COUNT(*) as attempts FROM login_attempts
WHERE ip_address = ? AND attempt_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)");
$stmt->execute([$ip]);
$ipAttempts = $stmt->fetch()['attempts'];
// 检查用户名限制
$stmt = $db->prepare("SELECT COUNT(*) as attempts FROM login_attempts
WHERE username = ? AND attempt_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)");
$stmt->execute([$username]);
$userAttempts = $stmt->fetch()['attempts'];
// 如果IP或用户名尝试次数超过5次,则阻止登录
return $ipAttempts >= 5 || $userAttempts >= 5;
}
function clearLoginAttempts($username) {
$ip = $_SERVER['REMOTE_ADDR'];
$db = getDBConnection();
$stmt = $db->prepare("DELETE FROM login_attempts WHERE ip_address = ? OR username = ?");
$stmt->execute([$ip, $username]);
}
// includes/functions.php
function redirectWithError($message) {
$_SESSION['login_error'] = $message;
header("Location: login.php");
exit;
}
function displayError() {
if (isset($_SESSION['login_error'])) {
echo '<div class="alert alert-danger">' . htmlspecialchars($_SESSION['login_error']) . '</div>';
unset($_SESSION['login_error']);
}
}
// 在登录表单中添加错误显示
<div class="mb-3">
<?php displayError(); ?>
</div>
// authenticate.php (完整版)
<?php
require_once 'config/database.php';
require_once 'includes/functions.php';
require_once 'includes/session.php';
require_once 'includes/auth_functions.php';
require_once 'includes/security.php';
require_once 'includes/remember_me.php';
// 检查登录尝试限制
if (isLoginBlocked($_POST['username'] ?? '')) {
redirectWithError('登录尝试次数过多,请1小时后再试');
}
// CSRF验证
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
redirectWithError('无效的请求');
}
// 获取输入
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
// 验证输入
if (empty($username) || empty($password)) {
redirectWithError('用户名和密码不能为空');
}
try {
$db = getDBConnection();
$stmt = $db->prepare("SELECT * FROM users WHERE username = :username OR email = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
if (!$user || !verifyPassword($password, $user['password_hash'])) {
logFailedAttempt($username);
redirectWithError('用户名或密码错误');
}
if (!$user['is_active']) {
redirectWithError('您的账户已被禁用');
}
// 登录成功
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['username'];
// 更新最后登录时间
$stmt = $db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?");
$stmt->execute([$user['id']]);
// 清除失败的登录尝试
clearLoginAttempts($username);
// 记住我功能
if ($remember) {
setRememberMeCookie($user['id']);
}
// 重定向到目标页面
$redirect = $_SESSION['redirect_url'] ?? 'dashboard.php';
unset($_SESSION['redirect_url']);
header("Location: " . $redirect);
exit;
} catch (PDOException $e) {
error_log("Login error: " . $e->getMessage());
redirectWithError('系统错误,请稍后再试');
}
我们完成了一个完整的PHP登录系统,包含以下功能:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。