用户认证基础
什么是用户认证?
用户认证是验证用户身份的过程,确保只有合法的用户才能访问受保护的资源。在Web应用中,用户认证通常包括以下几个核心功能:
- 用户注册:创建新用户账户
- 用户登录:验证用户凭据
- 会话管理:维持用户登录状态
- 权限控制:根据用户角色控制访问权限
- 密码安全:安全存储和验证密码
认证系统的基本组件
1. 用户数据存储
<?php
// 用户数据表结构示例
/*
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role ENUM('admin', 'user', 'guest') DEFAULT 'user',
status ENUM('active', 'inactive', 'suspended') DEFAULT 'inactive',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
last_login TIMESTAMP NULL,
login_attempts INT DEFAULT 0,
locked_until TIMESTAMP NULL
);
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
phone VARCHAR(20),
avatar_url VARCHAR(255),
bio TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
*/
?>
2. 密码哈希处理
<?php
// 密码处理类
class PasswordManager {
// 创建密码哈希
public static function hashPassword($password) {
// 使用PASSWORD_DEFAULT算法(目前是bcrypt)
return password_hash($password, PASSWORD_DEFAULT);
}
// 验证密码
public static function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
// 检查密码是否需要重新哈希
public static function needsRehash($hash) {
return password_needs_rehash($hash, PASSWORD_DEFAULT);
}
// 生成安全密码
public static function generateSecurePassword($length = 12) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()';
$password = '';
$characterCount = strlen($characters);
for ($i = 0; $i < $length; $i++) {
$password .= $characters[rand(0, $characterCount - 1)];
}
return $password;
}
// 验证密码强度
public static function validatePasswordStrength($password) {
$errors = [];
// 长度检查
if (strlen($password) < 8) {
$errors[] = "密码长度至少为8个字符";
}
// 包含大写字母
if (!preg_match('/[A-Z]/', $password)) {
$errors[] = "密码必须包含至少一个大写字母";
}
// 包含小写字母
if (!preg_match('/[a-z]/', $password)) {
$errors[] = "密码必须包含至少一个小写字母";
}
// 包含数字
if (!preg_match('/[0-9]/', $password)) {
$errors[] = "密码必须包含至少一个数字";
}
// 包含特殊字符
if (!preg_match('/[!@#$%^&*()\-_+=]/', $password)) {
$errors[] = "密码必须包含至少一个特殊字符";
}
return $errors;
}
}
// 使用示例
$password = "MySecurePassword123!";
$hash = PasswordManager::hashPassword($password);
echo "密码哈希:" . $hash . "<br>";
// 验证密码
if (PasswordManager::verifyPassword("MySecurePassword123!", $hash)) {
echo "密码验证成功<br>";
} else {
echo "密码验证失败<br>";
}
// 生成安全密码
$securePassword = PasswordManager::generateSecurePassword(16);
echo "生成的安全密码:" . $securePassword . "<br>";
// 验证密码强度
$strengthErrors = PasswordManager::validatePasswordStrength($password);
if (empty($strengthErrors)) {
echo "密码强度符合要求<br>";
} else {
echo "密码强度问题:" . implode(', ', $strengthErrors) . "<br>";
}
?>
3. 用户注册系统
<?php
// 用户注册类
class UserRegistration {
private $db;
private $errors = [];
public function __construct($database) {
$this->db = $database;
}
// 注册新用户
public function register($userData) {
// 验证输入数据
if (!$this->validateInput($userData)) {
return false;
}
// 检查用户名是否已存在
if ($this->isUsernameExists($userData['username'])) {
$this->errors[] = "用户名已存在";
return false;
}
// 检查邮箱是否已存在
if ($this->isEmailExists($userData['email'])) {
$this->errors[] = "邮箱已被注册";
return false;
}
// 验证密码强度
$passwordErrors = PasswordManager::validatePasswordStrength($userData['password']);
if (!empty($passwordErrors)) {
$this->errors = array_merge($this->errors, $passwordErrors);
return false;
}
// 创建用户
$userId = $this->createUser($userData);
if ($userId) {
// 创建用户资料
$this->createUserProfile($userId, $userData);
// 发送验证邮件
$this->sendVerificationEmail($userData['email'], $userData['username'], $userId);
return $userId;
}
return false;
}
// 验证输入数据
private function validateInput($userData) {
$this->errors = [];
// 验证用户名
if (empty($userData['username'])) {
$this->errors[] = "用户名不能为空";
} elseif (strlen($userData['username']) < 3 || strlen($userData['username']) > 50) {
$this->errors[] = "用户名长度必须在3-50个字符之间";
} elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $userData['username'])) {
$this->errors[] = "用户名只能包含字母、数字和下划线";
}
// 验证邮箱
if (empty($userData['email'])) {
$this->errors[] = "邮箱不能为空";
} elseif (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
$this->errors[] = "邮箱格式不正确";
}
// 验证密码
if (empty($userData['password'])) {
$this->errors[] = "密码不能为空";
}
// 验证确认密码
if (isset($userData['confirm_password']) &&
$userData['password'] !== $userData['confirm_password']) {
$this->errors[] = "两次输入的密码不一致";
}
return empty($this->errors);
}
// 检查用户名是否存在
private function isUsernameExists($username) {
$stmt = $this->db->prepare("SELECT id FROM users WHERE username = :username");
$stmt->execute([':username' => $username]);
return $stmt->fetch() !== false;
}
// 检查邮箱是否存在
private function isEmailExists($email) {
$stmt = $this->db->prepare("SELECT id FROM users WHERE email = :email");
$stmt->execute([':email' => $email]);
return $stmt->fetch() !== false;
}
// 创建用户记录
private function createUser($userData) {
$hashedPassword = PasswordManager::hashPassword($userData['password']);
$verificationToken = bin2hex(random_bytes(32));
$stmt = $this->db->prepare("
INSERT INTO users (username, email, password_hash, role, status, verification_token)
VALUES (:username, :email, :password_hash, :role, :status, :verification_token)
");
$result = $stmt->execute([
':username' => $userData['username'],
':email' => $userData['email'],
':password_hash' => $hashedPassword,
':role' => 'user',
':status' => 'inactive', // 需要邮箱验证后激活
':verification_token' => $verificationToken
]);
if ($result) {
return $this->db->lastInsertId();
}
return false;
}
// 创建用户资料
private function createUserProfile($userId, $userData) {
$stmt = $this->db->prepare("
INSERT INTO user_profiles (user_id, first_name, last_name, phone)
VALUES (:user_id, :first_name, :last_name, :phone)
");
return $stmt->execute([
':user_id' => $userId,
':first_name' => $userData['first_name'] ?? '',
':last_name' => $userData['last_name'] ?? '',
':phone' => $userData['phone'] ?? ''
]);
}
// 发送验证邮件
private function sendVerificationEmail($email, $username, $userId) {
// 这里应该实现邮件发送功能
// 可以使用PHPMailer或其他邮件库
$verificationLink = "http://yourdomain.com/verify.php?token=" .
urlencode($this->getVerificationToken($userId));
$subject = "验证您的账户";
$message = "
<h2>欢迎注册,{$username}!</h2>
<p>感谢您注册我们的网站。请点击下面的链接验证您的邮箱地址:</p>
<p><a href='{$verificationLink}'>验证邮箱</a></p>
<p>如果链接无法点击,请复制以下网址到浏览器地址栏:</p>
<p>{$verificationLink}</p>
<p>此链接将在24小时后失效。</p>
";
// 实际实现中,这里应该调用邮件发送函数
// sendEmail($email, $subject, $message);
// 为了演示,我们只记录邮件内容
echo "验证邮件已准备发送到:{$email}<br>";
echo "验证链接:{$verificationLink}<br>";
return true;
}
// 获取验证令牌
private function getVerificationToken($userId) {
$stmt = $this->db->prepare("SELECT verification_token FROM users WHERE id = :id");
$stmt->execute([':id' => $userId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ? $result['verification_token'] : null;
}
// 验证邮箱
public function verifyEmail($token) {
$stmt = $this->db->prepare("
SELECT id FROM users
WHERE verification_token = :token
AND status = 'inactive'
");
$stmt->execute([':token' => $token]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
$stmt = $this->db->prepare("
UPDATE users
SET status = 'active', verification_token = NULL, email_verified_at = NOW()
WHERE id = :id
");
return $stmt->execute([':id' => $user['id']]);
}
return false;
}
// 获取错误信息
public function getErrors() {
return $this->errors;
}
}
// 使用示例
/*
// 数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'username', 'password');
// 处理注册表单
if ($_POST['action'] === 'register') {
$registration = new UserRegistration($pdo);
$userData = [
'username' => $_POST['username'],
'email' => $_POST['email'],
'password' => $_POST['password'],
'confirm_password' => $_POST['confirm_password'],
'first_name' => $_POST['first_name'],
'last_name' => $_POST['last_name'],
'phone' => $_POST['phone']
];
$userId = $registration->register($userData);
if ($userId) {
echo "注册成功!请查收邮件验证您的账户。";
} else {
$errors = $registration->getErrors();
echo "注册失败:" . implode(', ', $errors);
}
}
// 处理邮箱验证
if ($_GET['action'] === 'verify' && isset($_GET['token'])) {
$registration = new UserRegistration($pdo);
if ($registration->verifyEmail($_GET['token'])) {
echo "邮箱验证成功!您的账户已激活。";
} else {
echo "验证链接无效或已过期。";
}
}
*/
?>
4. 用户登录系统
<?php
// 用户登录认证类
class UserAuthentication {
private $db;
private $maxLoginAttempts = 5;
private $lockoutDuration = 900; // 15分钟
private $sessionTimeout = 3600; // 1小时
public function __construct($database) {
$this->db = $database;
$this->initializeSession();
}
// 初始化Session
private function initializeSession() {
if (session_status() !== PHP_SESSION_ACTIVE) {
// 安全的Session配置
ini_set('session.cookie_httponly', 1);
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'Strict');
session_start();
session_regenerate_id(true);
}
}
// 用户登录
public function login($username, $password, $remember = false) {
// 获取用户信息
$user = $this->getUserByUsername($username);
if (!$user) {
$this->recordFailedLogin($username);
return ['success' => false, 'message' => '用户名或密码错误'];
}
// 检查账户状态
if ($user['status'] !== 'active') {
return ['success' => false, 'message' => '账户未激活或已被禁用'];
}
// 检查账户是否被锁定
if ($user['locked_until'] && strtotime($user['locked_until']) > time()) {
$remainingTime = strtotime($user['locked_until']) - time();
return [
'success' => false,
'message' => "账户已被锁定,请 {$remainingTime} 秒后重试"
];
}
// 验证密码
if (!PasswordManager::verifyPassword($password, $user['password_hash'])) {
$this->recordFailedLogin($username, $user['id'], $user['login_attempts']);
return ['success' => false, 'message' => '用户名或密码错误'];
}
// 登录成功
$this->recordSuccessfulLogin($user['id']);
$this->createUserSession($user, $remember);
return ['success' => true, 'user' => $user];
}
// 获取用户信息
private function getUserByUsername($username) {
$stmt = $this->db->prepare("
SELECT id, username, email, password_hash, role, status,
login_attempts, locked_until, last_login
FROM users
WHERE username = :username
");
$stmt->execute([':username' => $username]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// 记录登录失败
private function recordFailedLogin($username, $userId = null, $currentAttempts = 0) {
if ($userId) {
$newAttempts = $currentAttempts + 1;
$lockedUntil = null;
// 检查是否需要锁定账户
if ($newAttempts >= $this->maxLoginAttempts) {
$lockedUntil = date('Y-m-d H:i:s', time() + $this->lockoutDuration);
}
$stmt = $this->db->prepare("
UPDATE users
SET login_attempts = :attempts, locked_until = :locked_until
WHERE id = :user_id
");
$stmt->execute([
':attempts' => $newAttempts,
':locked_until' => $lockedUntil,
':user_id' => $userId
]);
}
// 记录安全日志
$this->logSecurityEvent('login_failed', [
'username' => $username,
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT']
]);
}
// 记录登录成功
private function recordSuccessfulLogin($userId) {
$stmt = $this->db->prepare("
UPDATE users
SET login_attempts = 0, locked_until = NULL, last_login = NOW()
WHERE id = :user_id
");
$stmt->execute([':user_id' => $userId]);
// 记录安全日志
$this->logSecurityEvent('login_success', [
'user_id' => $userId,
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT']
]);
}
// 创建用户Session
private function createUserSession($user, $remember = false) {
// 重新生成Session ID防止Session固定攻击
session_regenerate_id(true);
// 设置Session数据
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['email'] = $user['email'];
$_SESSION['role'] = $user['role'];
$_SESSION['login_time'] = time();
$_SESSION['last_activity'] = time();
$_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
// 设置记住我Cookie
if ($remember) {
$token = bin2hex(random_bytes(32));
$expires = time() + (86400 * 30); // 30天
$this->setRememberToken($user['id'], $token, $expires);
setcookie('remember_token', $token, $expires, '/', '', false, true);
}
}
// 设置记住我令牌
private function setRememberToken($userId, $token, $expires) {
$stmt = $this->db->prepare("
INSERT INTO remember_tokens (user_id, token, expires)
VALUES (:user_id, :token, :expires)
");
return $stmt->execute([
':user_id' => $userId,
':token' => $token,
':expires' => date('Y-m-d H:i:s', $expires)
]);
}
// 检查用户是否已登录
public function isLoggedIn() {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
// 检查Session是否包含用户信息
if (!isset($_SESSION['user_id'])) {
// 尝试使用记住我功能
return $this->checkRememberToken();
}
// 检查Session是否超时
if (time() - $_SESSION['last_activity'] > $this->sessionTimeout) {
$this->logout();
return false;
}
// 验证IP地址和User-Agent(可选的安全检查)
if ($this->validateSessionIntegrity()) {
// 更新最后活动时间
$_SESSION['last_activity'] = time();
return true;
}
$this->logout();
return false;
}
// 检查记住我令牌
private function checkRememberToken() {
if (!isset($_COOKIE['remember_token'])) {
return false;
}
$token = $_COOKIE['remember_token'];
$stmt = $this->db->prepare("
SELECT user_id FROM remember_tokens
WHERE token = :token AND expires > :now
");
$stmt->execute([':token' => $token, ':now' => date('Y-m-d H:i:s')]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$result) {
return false;
}
// 获取用户信息并创建Session
$user = $this->getUserById($result['user_id']);
if ($user && $user['status'] === 'active') {
session_regenerate_id(true);
$this->createUserSession($user, false);
return true;
}
return false;
}
// 根据ID获取用户信息
private function getUserById($userId) {
$stmt = $this->db->prepare("
SELECT id, username, email, role, status
FROM users
WHERE id = :id AND status = 'active'
");
$stmt->execute([':id' => $userId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// 验证Session完整性
private function validateSessionIntegrity() {
// 检查IP地址(可选,可能会影响移动用户)
if (isset($_SESSION['ip_address']) &&
$_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR']) {
$this->logSecurityEvent('ip_address_changed', [
'session_id' => session_id(),
'original_ip' => $_SESSION['ip_address'],
'current_ip' => $_SERVER['REMOTE_ADDR']
]);
return false;
}
// 检查User-Agent
if (isset($_SESSION['user_agent']) &&
$_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
$this->logSecurityEvent('user_agent_changed', [
'session_id' => session_id(),
'original_agent' => $_SESSION['user_agent'],
'current_agent' => $_SERVER['HTTP_USER_AGENT']
]);
return false;
}
return true;
}
// 获取当前用户信息
public function getCurrentUser() {
if ($this->isLoggedIn()) {
return [
'id' => $_SESSION['user_id'],
'username' => $_SESSION['username'],
'email' => $_SESSION['email'],
'role' => $_SESSION['role'],
'login_time' => $_SESSION['login_time']
];
}
return null;
}
// 检查用户权限
public function hasRole($requiredRole) {
if (!$this->isLoggedIn()) {
return false;
}
$userRole = $_SESSION['role'];
// 角色层次:admin > manager > user > guest
$roleHierarchy = [
'admin' => 4,
'manager' => 3,
'user' => 2,
'guest' => 1
];
return ($roleHierarchy[$userRole] ?? 0) >= ($roleHierarchy[$requiredRole] ?? 0);
}
// 用户登出
public function logout() {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$userId = $_SESSION['user_id'] ?? null;
// 记录登出事件
if ($userId) {
$this->logSecurityEvent('logout', [
'user_id' => $userId,
'session_duration' => time() - ($_SESSION['login_time'] ?? time())
]);
}
// 清除Session数据
$_SESSION = [];
// 删除Session Cookie
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
// 删除记住我令牌
if (isset($_COOKIE['remember_token'])) {
$token = $_COOKIE['remember_token'];
$stmt = $this->db->prepare("DELETE FROM remember_tokens WHERE token = :token");
$stmt->execute([':token' => $token]);
setcookie('remember_token', '', time() - 3600, '/');
}
// 销毁Session
session_destroy();
}
// 记录安全事件
private function logSecurityEvent($event, $data = []) {
$logEntry = [
'timestamp' => date('Y-m-d H:i:s'),
'event' => $event,
'data' => $data,
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
];
// 这里可以写入日志文件或数据库
error_log("Security Event: " . json_encode($logEntry));
}
// 获取登录统计信息
public function getLoginStats($userId) {
$stmt = $this->db->prepare("
SELECT last_login, login_attempts, status
FROM users
WHERE id = :user_id
");
$stmt->execute([':user_id' => $userId]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
return [
'last_login' => $user['last_login'],
'login_attempts' => $user['login_attempts'],
'status' => $user['status'],
'is_locked' => $user['locked_until'] && strtotime($user['locked_until']) > time(),
'locked_until' => $user['locked_until']
];
}
return null;
}
}
// 使用示例
/*
// 数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'username', 'password');
$auth = new UserAuthentication($pdo);
// 处理登录请求
if ($_POST['action'] === 'login') {
$username = $_POST['username'];
$password = $_POST['password'];
$remember = isset($_POST['remember']);
$result = $auth->login($username, $password, $remember);
if ($result['success']) {
echo "登录成功!欢迎," . $result['user']['username'] . "!";
// 重定向到仪表板
// header('Location: /dashboard');
} else {
echo "登录失败:" . $result['message'];
}
}
// 检查登录状态
if ($auth->isLoggedIn()) {
$user = $auth->getCurrentUser();
echo "当前用户:" . $user['username'] . "(" . $user['role'] . ")";
// 权限检查示例
if ($auth->hasRole('admin')) {
echo " - 您有管理员权限";
}
} else {
echo "请先登录!";
// 显示登录表单
}
// 处理登出请求
if ($_GET['action'] === 'logout') {
$auth->logout();
echo "已安全退出登录";
}
*/
?>
5. 权限控制系统
<?php
// 权限控制类
class AccessControl {
private $auth;
private $permissions = [];
public function __construct($auth) {
$this->auth = $auth;
$this->initializePermissions();
}
// 初始化权限定义
private function initializePermissions() {
$this->permissions = [
// 管理员权限
'admin' => [
'user.create',
'user.read',
'user.update',
'user.delete',
'content.create',
'content.read',
'content.update',
'content.delete',
'system.settings',
'system.logs',
'system.backup'
],
// 经理权限
'manager' => [
'user.read',
'user.update',
'content.create',
'content.read',
'content.update',
'content.delete',
'reports.view'
],
// 普通用户权限
'user' => [
'content.read',
'content.create',
'profile.update',
'profile.read'
],
// 访客权限
'guest' => [
'content.read'
]
];
}
// 检查用户是否有特定权限
public function can($permission) {
if (!$this->auth->isLoggedIn()) {
// 检查是否为游客权限
return in_array($permission, $this->permissions['guest'] ?? []);
}
$user = $this->auth->getCurrentUser();
$userRole = $user['role'];
return in_array($permission, $this->permissions[$userRole] ?? []);
}
// 检查用户是否为管理员
public function isAdmin() {
return $this->auth->hasRole('admin');
}
// 检查用户是否为经理
public function isManager() {
return $this->auth->hasRole('manager') || $this->auth->hasRole('admin');
}
// 检查用户是否可以访问特定资源
public function canAccessResource($resource, $action = 'read') {
$permission = $resource . '.' . $action;
return $this->can($permission);
}
// 获取用户的所有权限
public function getUserPermissions() {
if (!$this->auth->isLoggedIn()) {
return $this->permissions['guest'] ?? [];
}
$user = $this->auth->getCurrentUser();
$userRole = $user['role'];
return $this->permissions[$userRole] ?? [];
}
// 权限装饰器函数
public function requirePermission($permission, $callback = null) {
if (!$this->can($permission)) {
$this->accessDenied();
return false;
}
if ($callback && is_callable($callback)) {
return call_user_func($callback);
}
return true;
}
// 访问被拒绝处理
public function accessDenied() {
http_response_code(403);
if ($this->auth->isLoggedIn()) {
echo "<h1>访问被拒绝</h1>";
echo "<p>您没有足够的权限访问此资源。</p>";
echo "<p><a href='/'>返回首页</a></p>";
} else {
echo "<h1>请先登录</h1>";
echo "<p>您需要登录才能访问此资源。</p>";
echo "<p><a href='/login.php'>登录</a></p>";
}
exit;
}
// 角色中间件
public function requireRole($role) {
if (!$this->auth->hasRole($role)) {
$this->accessDenied();
}
}
// 资源访问检查中间件
public function checkResourceAccess($resource, $action = 'read') {
if (!$this->canAccessResource($resource, $action)) {
$this->accessDenied();
}
}
// 动态权限检查(基于数据库)
public function canDo($action, $resourceType = null, $resourceId = null) {
if (!$this->auth->isLoggedIn()) {
return false;
}
$user = $this->auth->getCurrentUser();
$userId = $user['id'];
// 管理员可以执行任何操作
if ($this->isAdmin()) {
return true;
}
// 这里可以实现更复杂的权限逻辑
// 例如基于资源的所有者检查
if ($resourceType === 'user' && $action === 'update') {
// 用户只能更新自己的资料
return $userId == $resourceId;
}
if ($resourceType === 'content') {
// 内容创建者可以编辑和删除
return $this->isContentOwner($userId, $resourceId, $action);
}
return false;
}
// 检查内容所有权
private function isContentOwner($userId, $contentId, $action) {
// 这里应该查询数据库检查内容所有权
// 为了演示,返回false
return false;
}
}
// 权限装饰器函数示例
function requirePermission($permission) {
global $auth, $accessControl;
if (!$accessControl->can($permission)) {
$accessControl->accessDenied();
}
}
function requireRole($role) {
global $auth, $accessControl;
if (!$auth->hasRole($role)) {
$accessControl->accessDenied();
}
}
// 使用示例
/*
// 初始化
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'username', 'password');
$auth = new UserAuthentication($pdo);
$accessControl = new AccessControl($auth);
// 在控制器中使用权限检查
class UserController {
private $accessControl;
public function __construct($accessControl) {
$this->accessControl = $accessControl;
}
public function createUser() {
// 检查权限
$this->accessControl->requirePermission('user.create');
// 创建用户逻辑
echo "创建用户界面";
}
public function updateUser($userId) {
// 动态权限检查
if (!$this->accessControl->canDo('update', 'user', $userId)) {
$this->accessControl->accessDenied();
}
// 更新用户逻辑
echo "更新用户 {$userId} 的信息";
}
public function deleteUser($userId) {
// 检查权限
$this->accessControl->requirePermission('user.delete');
// 删除用户逻辑
echo "删除用户 {$userId}";
}
}
// 在路由中使用权限检查
function handleRequest($route, $action) {
global $accessControl;
// 权限检查
$accessControl->checkResourceAccess($route, $action);
// 继续处理请求
echo "处理路由:{$route},操作:{$action}";
}
// 在视图模板中使用权限检查
// 检查用户权限来显示/隐藏界面元素
function renderNavigation() {
global $accessControl;
echo "<nav>";
echo "<a href='/'>首页</a> ";
echo "<a href='/dashboard'>仪表板</a> ";
// 只有管理员可以看到用户管理
if ($accessControl->can('user.read')) {
echo "<a href='/users'>用户管理</a> ";
}
// 只有管理员可以看到系统设置
if ($accessControl->can('system.settings')) {
echo "<a href='/settings'>系统设置</a> ";
}
echo "</nav>";
}
// 处理具体的页面请求
if ($_GET['page'] === 'users') {
requirePermission('user.read');
// 显示用户列表
}
if ($_GET['page'] === 'settings') {
requirePermission('system.settings');
// 显示系统设置
}
if ($_GET['page'] === 'profile') {
// 用户资料页面,用户自己可以访问
$userId = $_GET['id'] ?? $_SESSION['user_id'];
if (!$accessControl->canDo('update', 'user', $userId)) {
$accessControl->accessDenied();
}
// 显示用户资料
}
*/
?>
安全最佳实践
1. 输入验证和过滤
<?php
// 输入验证类
class InputValidator {
// 清理和验证用户输入
public static function sanitize($input, $type = 'string') {
if (is_array($input)) {
return array_map(function($item) use ($type) {
return self::sanitize($item, $type);
}, $input);
}
switch ($type) {
case 'email':
return filter_var($input, FILTER_SANITIZE_EMAIL);
case 'url':
return filter_var($input, FILTER_SANITIZE_URL);
case 'int':
return filter_var($input, FILTER_SANITIZE_NUMBER_INT);
case 'float':
return filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT);
case 'string':
default:
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
}
// 验证用户名
public static function validateUsername($username) {
$errors = [];
if (empty($username)) {
$errors[] = "用户名不能为空";
} elseif (strlen($username) < 3 || strlen($username) > 50) {
$errors[] = "用户名长度必须在3-50个字符之间";
} elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
$errors[] = "用户名只能包含字母、数字和下划线";
}
return $errors;
}
// 验证邮箱
public static function validateEmail($email) {
$errors = [];
if (empty($email)) {
$errors[] = "邮箱不能为空";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "邮箱格式不正确";
}
return $errors;
}
// 验证密码
public static function validatePassword($password) {
$errors = [];
if (empty($password)) {
$errors[] = "密码不能为空";
} elseif (strlen($password) < 8) {
$errors[] = "密码长度至少为8个字符";
}
return $errors;
}
// 验证CSRF令牌
public static function validateCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
// 生成CSRF令牌
public static function generateCSRFToken() {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
}
?>
2. SQL注入防护
<?php
// 安全的数据库操作类
class SecureDatabase {
private $pdo;
public function __construct($dsn, $username, $password) {
$this->pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
// 安全的用户查询
public function getUserByUsername($username) {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute([':username' => $username]);
return $stmt->fetch();
}
// 安全的用户搜索(防止SQL注入)
public function searchUsers($searchTerm) {
$searchTerm = "%{$searchTerm}%";
$stmt = $this->pdo->prepare("
SELECT id, username, email FROM users
WHERE username LIKE :search OR email LIKE :search
LIMIT 50
");
$stmt->execute([':search' => $searchTerm]);
return $stmt->fetchAll();
}
// 分页查询示例
public function getUsersWithPagination($page = 1, $perPage = 10, $filters = []) {
$offset = ($page - 1) * $perPage;
// 构建WHERE子句
$where = "WHERE 1=1";
$params = [];
if (!empty($filters['role'])) {
$where .= " AND role = :role";
$params[':role'] = $filters['role'];
}
if (!empty($filters['status'])) {
$where .= " AND status = :status";
$params[':status'] = $filters['status'];
}
// 获取总数
$countStmt = $this->pdo->prepare("SELECT COUNT(*) as total FROM users {$where}");
$countStmt->execute($params);
$total = $countStmt->fetch()['total'];
// 获取分页数据
$dataStmt = $this->pdo->prepare("
SELECT id, username, email, role, status, created_at
FROM users {$where}
ORDER BY created_at DESC
LIMIT :per_page OFFSET :offset
");
$dataStmt->execute(array_merge($params, [
':per_page' => $perPage,
':offset' => $offset
]));
return [
'data' => $dataStmt->fetchAll(),
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'total_pages' => ceil($total / $perPage)
];
}
}
?>
3. XSS攻击防护
<?php
// XSS防护类
class XSSProtection {
// 输出转义
public static function escape($string) {
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
// 安全地输出用户生成的内容
public static function safeEcho($string) {
echo self::escape($string);
}
// 清理HTML内容
public static function cleanHTML($html) {
// 允许的HTML标签
$allowedTags = '<p><br><strong><em><u><ol><ul><li><a><h1><h2><h3><h4><h5><h6>';
return strip_tags($html, $allowedTags);
}
// JSON输出转义
public static function jsonEscape($data) {
return json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
}
// URL编码
public static function urlEncode($string) {
return urlencode($string);
}
// 安全的属性值
public static function safeAttribute($string) {
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}
?>
完整的用户认证系统示例
<?php
// 完整的用户认证系统整合
class AuthenticationSystem {
private $db;
private $auth;
private $registration;
private $accessControl;
public function __construct($dbConfig) {
$this->db = new PDO(
$dbConfig['dsn'],
$dbConfig['username'],
$dbConfig['password'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
$this->auth = new UserAuthentication($this->db);
$this->registration = new UserRegistration($this->db);
$this->accessControl = new AccessControl($this->auth);
}
// 处理注册请求
public function handleRegistration($postData) {
$errors = [];
// 验证CSRF令牌
if (!InputValidator::validateCSRFToken($postData['csrf_token'] ?? '')) {
$errors[] = "安全验证失败,请重新提交";
return ['success' => false, 'errors' => $errors];
}
// 清理输入数据
$userData = [
'username' => InputValidator::sanitize($postData['username'] ?? ''),
'email' => InputValidator::sanitize($postData['email'] ?? ''),
'password' => $postData['password'] ?? '',
'confirm_password' => $postData['confirm_password'] ?? '',
'first_name' => InputValidator::sanitize($postData['first_name'] ?? ''),
'last_name' => InputValidator::sanitize($postData['last_name'] ?? ''),
'phone' => InputValidator::sanitize($postData['phone'] ?? '')
];
// 额外验证
$usernameErrors = InputValidator::validateUsername($userData['username']);
$emailErrors = InputValidator::validateEmail($userData['email']);
if (!empty($usernameErrors)) {
$errors = array_merge($errors, $usernameErrors);
}
if (!empty($emailErrors)) {
$errors = array_merge($errors, $emailErrors);
}
if (!empty($errors)) {
return ['success' => false, 'errors' => $errors];
}
// 注册用户
$userId = $this->registration->register($userData);
if ($userId) {
return [
'success' => true,
'message' => '注册成功!请查收邮件验证您的账户。',
'user_id' => $userId
];
} else {
return [
'success' => false,
'errors' => $this->registration->getErrors()
];
}
}
// 处理登录请求
public function handleLogin($postData) {
$errors = [];
// 验证CSRF令牌
if (!InputValidator::validateCSRFToken($postData['csrf_token'] ?? '')) {
$errors[] = "安全验证失败,请重新提交";
return ['success' => false, 'errors' => $errors];
}
// 清理输入数据
$username = InputValidator::sanitize($postData['username'] ?? '');
$password = $postData['password'] ?? '';
$remember = isset($postData['remember']);
if (empty($username) || empty($password)) {
$errors[] = "用户名和密码不能为空";
return ['success' => false, 'errors' => $errors];
}
// 尝试登录
$result = $this->auth->login($username, $password, $remember);
return $result;
}
// 获取认证实例
public function getAuth() {
return $this->auth;
}
// 获取访问控制实例
public function getAccessControl() {
return $this->accessControl;
}
// 渲染登录表单
public function renderLoginForm() {
$csrfToken = InputValidator::generateCSRFToken();
echo '<form method="POST" action="/login" class="login-form">';
echo '<input type="hidden" name="csrf_token" value="' . XSSProtection::escape($csrfToken) . '">';
echo '<div class="form-group">';
echo '<label for="username">用户名:</label>';
echo '<input type="text" id="username" name="username" required>';
echo '</div>';
echo '<div class="form-group">';
echo '<label for="password">密码:</label>';
echo '<input type="password" id="password" name="password" required>';
echo '</div>';
echo '<div class="form-group">';
echo '<label>';
echo '<input type="checkbox" name="remember" id="remember">';
echo ' 记住我';
echo '</label>';
echo '</div>';
echo '<button type="submit" class="btn btn-primary">登录</button>';
echo '</form>';
}
// 渲染注册表单
public function renderRegistrationForm() {
$csrfToken = InputValidator::generateCSRFToken();
echo '<form method="POST" action="/register" class="registration-form">';
echo '<input type="hidden" name="csrf_token" value="' . XSSProtection::escape($csrfToken) . '">';
echo '<div class="form-row">';
echo '<div class="form-group">';
echo '<label for="username">用户名:</label>';
echo '<input type="text" id="username" name="username" required>';
echo '</div>';
echo '<div class="form-group">';
echo '<label for="email">邮箱:</label>';
echo '<input type="email" id="email" name="email" required>';
echo '</div>';
echo '</div>';
echo '<div class="form-row">';
echo '<div class="form-group">';
echo '<label for="password">密码:</label>';
echo '<input type="password" id="password" name="password" required>';
echo '</div>';
echo '<div class="form-group">';
echo '<label for="confirm_password">确认密码:</label>';
echo '<input type="password" id="confirm_password" name="confirm_password" required>';
echo '</div>';
echo '</div>';
echo '<div class="form-row">';
echo '<div class="form-group">';
echo '<label for="first_name">名字:</label>';
echo '<input type="text" id="first_name" name="first_name">';
echo '</div>';
echo '<div class="form-group">';
echo '<label for="last_name">姓氏:</label>';
echo '<input type="text" id="last_name" name="last_name">';
echo '</div>';
echo '</div>';
echo '<div class="form-group">';
echo '<label for="phone">电话:</label>';
echo '<input type="tel" id="phone" name="phone">';
echo '</div>';
echo '<button type="submit" class="btn btn-primary">注册</button>';
echo '</form>';
}
// 渲染用户仪表板
public function renderDashboard() {
if (!$this->auth->isLoggedIn()) {
$this->redirectToLogin();
return;
}
$user = $this->auth->getCurrentUser();
$permissions = $this->accessControl->getUserPermissions();
echo '<div class="dashboard">';
echo '<h1>欢迎,' . XSSProtection::escape($user['username']) . '!</h1>';
echo '<div class="user-info">';
echo '<p><strong>角色:</strong>' . XSSProtection::escape($user['role']) . '</p>';
echo '<p><strong>邮箱:</strong>' . XSSProtection::escape($user['email']) . '</p>';
echo '<p><strong>登录时间:</strong>' . date('Y-m-d H:i:s', $user['login_time']) . '</p>';
echo '</div>';
// 根据权限显示不同的功能
echo '<div class="dashboard-menu">';
if ($this->accessControl->can('user.read')) {
echo '<a href="/users" class="menu-item">用户管理</a>';
}
if ($this->accessControl->can('content.create')) {
echo '<a href="/content/create" class="menu-item">创建内容</a>';
}
if ($this->accessControl->can('system.settings')) {
echo '<a href="/settings" class="menu-item">系统设置</a>';
}
echo '<a href="/profile" class="menu-item">个人资料</a>';
echo '<a href="/logout" class="menu-item">退出登录</a>';
echo '</div>';
echo '<div class="permissions">';
echo '<h3>您的权限:</h3>';
echo '<ul>';
foreach ($permissions as $permission) {
echo '<li>' . XSSProtection::escape($permission) . '</li>';
}
echo '</ul>';
echo '</div>';
echo '</div>';
}
// 重定向到登录页面
private function redirectToLogin() {
header('Location: /login');
exit;
}
// 处理登出
public function handleLogout() {
$this->auth->logout();
header('Location: /');
exit;
}
}
// 使用示例
/*
// 数据库配置
$dbConfig = [
'dsn' => 'mysql:host=localhost;dbname=myapp;charset=utf8mb4',
'username' => 'your_username',
'password' => 'your_password'
];
// 初始化认证系统
$authSystem = new AuthenticationSystem($dbConfig);
// 路由处理
$requestUri = $_SERVER['REQUEST_URI'];
$requestMethod = $_SERVER['REQUEST_METHOD'];
switch ($requestUri) {
case '/':
// 首页
echo '<h1>欢迎来到我们的网站</h1>';
if ($authSystem->getAuth()->isLoggedIn()) {
echo '<p><a href="/dashboard">进入仪表板</a></p>';
} else {
echo '<p><a href="/login">登录</a> | <a href="/register">注册</a></p>';
}
break;
case '/login':
if ($requestMethod === 'GET') {
$authSystem->renderLoginForm();
} elseif ($requestMethod === 'POST') {
$result = $authSystem->handleLogin($_POST);
if ($result['success']) {
header('Location: /dashboard');
exit;
} else {
echo '<div class="error">' . implode('<br>', $result['errors']) . '</div>';
$authSystem->renderLoginForm();
}
}
break;
case '/register':
if ($requestMethod === 'GET') {
$authSystem->renderRegistrationForm();
} elseif ($requestMethod === 'POST') {
$result = $authSystem->handleRegistration($_POST);
if ($result['success']) {
echo '<div class="success">' . $result['message'] . '</div>';
} else {
echo '<div class="error">' . implode('<br>', $result['errors']) . '</div>';
$authSystem->renderRegistrationForm();
}
}
break;
case '/dashboard':
$authSystem->renderDashboard();
break;
case '/logout':
$authSystem->handleLogout();
break;
default:
echo '<h1>页面未找到</h1>';
echo '<p><a href="/">返回首页</a></p>';
break;
}
*/
?>
总结
用户认证是Web应用安全的核心,通过实现完善的认证系统,你可以:
关键安全措施:
- 密码安全:使用强哈希算法存储密码
- Session管理:安全的会话处理和超时机制
- CSRF防护:防止跨站请求伪造攻击
- 输入验证:严格验证和过滤所有用户输入
- 权限控制:基于角色的访问控制
- 安全日志:记录重要的安全事件
最佳实践:
- 始终使用HTTPS传输敏感数据
- 实施多层安全防护
- 定期更新和审查安全策略
- 对用户进行安全教育
- 实施密码复杂度要求
- 定期备份数据
通过学习和实施这些用户认证技术,你可以构建安全、可靠的Web应用程序。记住,安全是一个持续的过程,需要不断学习和改进。