Skip to content

10.5 实操:用户登录系统

本实操将创建一个完整的用户登录系统,包含注册、登录、退出功能,以及会话管理和权限控制。

功能需求

  1. 用户注册:用户名、邮箱、密码
  2. 用户登录:用户名/邮箱、密码
  3. 退出登录
  4. 会话管理:登录状态保持、会话过期
  5. 权限控制:受保护页面只能登录用户访问

实现代码

1. 数据库准备

sql
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2. 配置文件

php
<?php
// config.php

// 数据库配置
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'test');

// 会话配置
define('SESSION_EXPIRY', 30 * 60); // 30分钟

// 网站配置
define('SITE_URL', 'http://localhost');
?>

3. 数据库连接

php
<?php
// db.php
require_once 'config.php';

function getDbConnection() {
    $conn = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
    if (!$conn) {
        die('数据库连接失败: ' . mysqli_connect_error());
    }
    mysqli_set_charset($conn, 'utf8');
    return $conn;
}
?>

4. 认证函数

php
<?php
// auth.php
require_once 'config.php';

/**
 * 启动会话
 */
function startSession() {
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
}

/**
 * 检查用户是否已登录
 * @return bool
 */
function isLoggedIn() {
    startSession();
    return isset($_SESSION['is_logged_in']) && $_SESSION['is_logged_in'];
}

/**
 * 获取当前登录用户信息
 * @return array|null
 */
function getCurrentUser() {
    if (!isLoggedIn()) {
        return null;
    }
    
    return [
        'id' => $_SESSION['user_id'] ?? null,
        'username' => $_SESSION['username'] ?? null,
        'email' => $_SESSION['email'] ?? null,
        'login_time' => $_SESSION['login_time'] ?? null
    ];
}

/**
 * 要求用户必须登录
 */
function requireLogin() {
    if (!isLoggedIn()) {
        // 存储当前页面,登录后重定向回来
        startSession();
        $_SESSION['redirect_url'] = $_SERVER['REQUEST_URI'];
        header('Location: login.php');
        exit;
    }
}

/**
 * 检查会话是否过期
 * @return bool
 */
function isSessionExpired() {
    startSession();
    if (!isset($_SESSION['last_activity'])) {
        return true;
    }
    
    return time() - $_SESSION['last_activity'] > SESSION_EXPIRY;
}

/**
 * 检查并处理会话
 */
function checkSession() {
    startSession();
    
    if (isLoggedIn() && isSessionExpired()) {
        // 会话过期,销毁会话
        logout();
        
        // 存储当前页面,登录后重定向回来
        $_SESSION['redirect_url'] = $_SERVER['REQUEST_URI'];
        header('Location: login.php');
        exit;
    } else if (isLoggedIn()) {
        // 更新最后活动时间
        $_SESSION['last_activity'] = time();
    }
}

/**
 * 登录用户
 * @param array $user 用户信息
 */
function login($user) {
    startSession();
    $_SESSION['user_id'] = $user['id'];
    $_SESSION['username'] = $user['username'];
    $_SESSION['email'] = $user['email'];
    $_SESSION['is_logged_in'] = true;
    $_SESSION['login_time'] = time();
    $_SESSION['last_activity'] = time();
}

/**
 * 退出登录
 */
function logout() {
    startSession();
    session_unset();
    session_destroy();
}
?>

5. 注册页面

php
<?php
// register.php
require_once 'auth.php';
require_once 'db.php';

// 检查是否已登录
if (isLoggedIn()) {
    header('Location: dashboard.php');
    exit;
}

$errors = [];
$success = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 表单验证
    if (empty($_POST['username'])) {
        $errors[] = '用户名不能为空';
    }
    
    if (empty($_POST['email'])) {
        $errors[] = '邮箱不能为空';
    } else if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
        $errors[] = '邮箱格式不正确';
    }
    
    if (empty($_POST['password'])) {
        $errors[] = '密码不能为空';
    } else if (strlen($_POST['password']) < 6) {
        $errors[] = '密码长度不能少于6位';
    }
    
    if (empty($errors)) {
        $username = htmlspecialchars($_POST['username']);
        $email = htmlspecialchars($_POST['email']);
        $password = password_hash($_POST['password'], PASSWORD_DEFAULT);
        
        // 检查用户名和邮箱是否已存在
        $conn = getDbConnection();
        $stmt = $conn->prepare('SELECT id FROM users WHERE username = ? OR email = ?');
        $stmt->bind_param('ss', $username, $email);
        $stmt->execute();
        $stmt->store_result();
        
        if ($stmt->num_rows > 0) {
            $errors[] = '用户名或邮箱已存在';
        } else {
            // 插入新用户
            $stmt = $conn->prepare('INSERT INTO users (username, email, password) VALUES (?, ?, ?)');
            $stmt->bind_param('sss', $username, $email, $password);
            
            if ($stmt->execute()) {
                $success = true;
            } else {
                $errors[] = '注册失败,请重试';
            }
        }
        
        $stmt->close();
        $conn->close();
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>用户注册</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 400px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; }
        .form-container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        h1 { text-align: center; color: #333; }
        .error { color: red; margin: 10px 0; }
        .success { color: green; margin: 10px 0; }
        input { width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
        input[type="submit"] { background-color: #4CAF50; color: white; border: none; cursor: pointer; }
        .login-link { margin-top: 15px; text-align: center; }
    </style>
</head>
<body>
    <div class="form-container">
        <h1>用户注册</h1>
        
        <?php if (!empty($errors)): ?>
            <div class="error">
                <ul>
                    <?php foreach ($errors as $error): ?>
                        <li><?php echo $error; ?></li>
                    <?php endforeach; ?>
                </ul>
            </div>
        <?php endif; ?>
        
        <?php if ($success): ?>
            <div class="success">
                注册成功!<a href="login.php">立即登录</a>
            </div>
        <?php else: ?>
            <form action="register.php" method="post">
                <label>用户名: <input type="text" name="username" value="<?php echo isset($_POST['username']) ? htmlspecialchars($_POST['username']) : ''; ?>"></label><br>
                <label>邮箱: <input type="email" name="email" value="<?php echo isset($_POST['email']) ? htmlspecialchars($_POST['email']) : ''; ?>"></label><br>
                <label>密码: <input type="password" name="password"></label><br>
                <input type="submit" value="注册">
            </form>
            
            <div class="login-link">
                已有账号?<a href="login.php">登录</a>
            </div>
        <?php endif; ?>
    </div>
</body>
</html>

6. 登录页面

php
<?php
// login.php
require_once 'auth.php';
require_once 'db.php';

// 检查是否已登录
if (isLoggedIn()) {
    header('Location: dashboard.php');
    exit;
}

$errors = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 表单验证
    if (empty($_POST['login'])) {
        $errors[] = '用户名/邮箱不能为空';
    }
    
    if (empty($_POST['password'])) {
        $errors[] = '密码不能为空';
    }
    
    if (empty($errors)) {
        $login = htmlspecialchars($_POST['login']);
        $password = $_POST['password'];
        
        // 查找用户
        $conn = getDbConnection();
        $stmt = $conn->prepare('SELECT id, username, email, password FROM users WHERE username = ? OR email = ?');
        $stmt->bind_param('ss', $login, $login);
        $stmt->execute();
        $result = $stmt->get_result();
        
        if ($user = $result->fetch_assoc()) {
            // 验证密码
            if (password_verify($password, $user['password'])) {
                // 登录成功
                login($user);
                
                // 重定向到之前的页面或仪表板
                $redirectUrl = $_SESSION['redirect_url'] ?? 'dashboard.php';
                unset($_SESSION['redirect_url']);
                header('Location: ' . $redirectUrl);
                exit;
            } else {
                $errors[] = '密码错误';
            }
        } else {
            $errors[] = '用户不存在';
        }
        
        $stmt->close();
        $conn->close();
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>用户登录</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 400px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; }
        .form-container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        h1 { text-align: center; color: #333; }
        .error { color: red; margin: 10px 0; }
        input { width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
        input[type="submit"] { background-color: #4CAF50; color: white; border: none; cursor: pointer; }
        .register-link { margin-top: 15px; text-align: center; }
    </style>
</head>
<body>
    <div class="form-container">
        <h1>用户登录</h1>
        
        <?php if (!empty($errors)): ?>
            <div class="error">
                <ul>
                    <?php foreach ($errors as $error): ?>
                        <li><?php echo $error; ?></li>
                    <?php endforeach; ?>
                </ul>
            </div>
        <?php endif; ?>
        
        <form action="login.php" method="post">
            <label>用户名/邮箱: <input type="text" name="login" value="<?php echo isset($_POST['login']) ? htmlspecialchars($_POST['login']) : ''; ?>"></label><br>
            <label>密码: <input type="password" name="password"></label><br>
            <input type="submit" value="登录">
        </form>
        
        <div class="register-link">
            还没有账号?<a href="register.php">注册</a>
        </div>
    </div>
</body>
</html>

7. 仪表板页面

php
<?php
// dashboard.php
require_once 'auth.php';

// 检查会话
checkSession();

// 要求登录
requireLogin();

// 获取当前用户
$user = getCurrentUser();
?>

<!DOCTYPE html>
<html>
<head>
    <title>仪表板</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; background-color: #f5f5f5; }
        .header { display: flex; justify-content: space-between; align-items: center; background-color: white; padding: 10px 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
        .welcome { font-size: 18px; }
        .logout { background-color: #f44336; color: white; padding: 10px; text-decoration: none; border-radius: 4px; }
        .content { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
    </style>
</head>
<body>
    <div class="header">
        <div class="welcome">
            欢迎,<?php echo $user['username']; ?><br>
            登录时间: <?php echo date('Y-m-d H:i:s', $user['login_time']); ?>
        </div>
        <a href="logout.php" class="logout">退出登录</a>
    </div>
    
    <div class="content">
        <h2>仪表板</h2>
        <p>这是登录后的页面,只有登录用户才能访问。</p>
        <p>用户信息:</p>
        <ul>
            <li>ID: <?php echo $user['id']; ?></li>
            <li>用户名: <?php echo $user['username']; ?></li>
            <li>邮箱: <?php echo $user['email']; ?></li>
        </ul>
    </div>
</body>
</html>

8. 退出登录页面

php
<?php
// logout.php
require_once 'auth.php';

// 退出登录
logout();

// 重定向到登录页面
header('Location: login.php');
exit;
?>

代码解析

  1. 配置文件:定义数据库连接信息和会话配置
  2. 数据库连接:提供数据库连接功能
  3. 认证函数:封装登录、退出、会话检查等功能
  4. 注册页面:处理用户注册,包括表单验证和数据库操作
  5. 登录页面:处理用户登录,包括身份验证和会话设置
  6. 仪表板页面:显示用户信息,只有登录用户才能访问
  7. 退出登录页面:销毁会话,重定向到登录页面

注意事项

  1. 安全性

    • 使用 password_hash() 存储密码
    • 验证用户输入,防止 SQL 注入和 XSS 攻击
    • 定期检查会话过期
  2. 用户体验

    • 提供清晰的错误提示
    • 登录后重定向到之前的页面
    • 会话过期后自动跳转到登录页面
  3. 性能

    • 使用预处理语句防止 SQL 注入
    • 避免在会话中存储大量数据

练习

  1. 实现密码重置功能
  2. 添加用户资料编辑功能
  3. 实现基于角色的权限控制
  4. 添加登录尝试限制,防止暴力破解

© 2026 编程马·菜鸟教程 版权所有