用户认证基础

什么是用户认证?

用户认证是验证用户身份的过程,确保只有合法的用户才能访问受保护的资源。在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应用安全的核心,通过实现完善的认证系统,你可以:

关键安全措施:

  1. 密码安全:使用强哈希算法存储密码
  2. Session管理:安全的会话处理和超时机制
  3. CSRF防护:防止跨站请求伪造攻击
  4. 输入验证:严格验证和过滤所有用户输入
  5. 权限控制:基于角色的访问控制
  6. 安全日志:记录重要的安全事件

最佳实践:

  • 始终使用HTTPS传输敏感数据
  • 实施多层安全防护
  • 定期更新和审查安全策略
  • 对用户进行安全教育
  • 实施密码复杂度要求
  • 定期备份数据

通过学习和实施这些用户认证技术,你可以构建安全、可靠的Web应用程序。记住,安全是一个持续的过程,需要不断学习和改进。