近在带几个新人做项目,发现很多初学者对“登录注册”这个最基础的功能,依然停留在“照着教程敲一遍,能跑就行”的阶段。但真要把这个模块做到安全、健壮、用户体验好,里面的门道其实不少。

正好之前写了一套基于PHP+MySQL+jQuery的登录注册系统(附完整源码),今天借这个机会,把整个设计和实现思路拆开揉碎了讲讲。希望看完这篇文章,你不仅能自己写出来,还能明白为什么这么写。

一、为什么还需要自己写登录注册?

很多框架(Laravel、Symfony)都内置了用户认证,甚至还有像JWT这样的现成方案。但为什么我依然建议初学者手写一遍?因为认证是一个系统的基石,你只有亲手处理过密码哈希、会话管理、SQL注入防护,才能真正理解框架替我们做了什么。

另外,在实际工作中,很多老旧项目或轻量级站点并不适合引入重型框架,手写一套轻量的认证模块往往更灵活。

二、技术栈选型:为什么是这些?

  • PHP 8.x:服务端语言,简单直接,部署方便。

  • MySQL:存储用户数据,关系型数据库成熟稳定。

  • jQuery + AJAX:用AJAX做异步提交,页面无刷新,体验流畅。jQuery虽然不再是前端主流,但在快速开发中依然很顺手。

  • HTML/CSS:基础结构,配合flex/grid做了简单的响应式设计。

这套组合“老套”但实用,尤其适合初学者理解前后端交互的全过程。

三、系统设计:前后端混合但分离交互

我们采用传统多页面架构 + 局部异步刷新:页面由后端返回(index.html),但表单提交走AJAX。这样既保持了简单性,又避免了表单提交后整页刷新的闪烁感。

四、部分页面展示:

符合现代审美的页面样式:

密码可显示和隐藏:

注册页面:

好了,下面来看看项目文件结构:

project/
├── index.html          # 登录注册页面
├── style.css           # 页面样式
├── config.php          # 数据库配置
├── register.php        # 注册处理
├── login.php           # 登录处理
└── dashboard.php       # 登录后的欢迎页(示例)

五、下面直接上代码:

1、数据库表结构 (MySQL)

CREATE DATABASE IF NOT EXISTS auth_demo;
USE auth_demo;

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、index.html (包含登录/注册表单和jQuery代码)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录注册系统</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="form-container">
            <div class="tabs">
                <button class="tab-btn active" id="login-tab">登录</button>
                <button class="tab-btn" id="register-tab">注册</button>
            </div>

            <!-- 登录表单 -->
            <form id="login-form" class="form active">
                <h2>登录</h2>
                <div class="form-group">
                    <label for="login-username">用户名</label>
                    <input type="text" id="login-username" name="username" required>
                </div>
                <div class="form-group">
                    <label for="login-password">密码</label>
                    <input type="password" id="login-password" name="password" required>
                </div>
                <button type="submit" class="submit-btn">登录</button>
                <div class="msg" id="login-msg"></div>
            </form>

            <!-- 注册表单 -->
            <form id="register-form" class="form">
                <h2>注册</h2>
                <div class="form-group">
                    <label for="reg-username">用户名</label>
                    <input type="text" id="reg-username" name="username" required>
                </div>
                <div class="form-group">
                    <label for="reg-email">邮箱</label>
                    <input type="email" id="reg-email" name="email" required>
                </div>
                <div class="form-group">
                    <label for="reg-password">密码</label>
                    <input type="password" id="reg-password" name="password" required>
                </div>
                <div class="form-group">
                    <label for="reg-confirm">确认密码</label>
                    <input type="password" id="reg-confirm" name="confirm" required>
                </div>
                <button type="submit" class="submit-btn">注册</button>
                <div class="msg" id="register-msg"></div>
            </form>
        </div>
    </div>

    <script>
    $(document).ready(function() {
        // 切换标签
        $('#login-tab').click(function() {
            $('#login-tab').addClass('active');
            $('#register-tab').removeClass('active');
            $('#login-form').addClass('active');
            $('#register-form').removeClass('active');
            clearMessages();
        });

        $('#register-tab').click(function() {
            $('#register-tab').addClass('active');
            $('#login-tab').removeClass('active');
            $('#register-form').addClass('active');
            $('#login-form').removeClass('active');
            clearMessages();
        });

        function clearMessages() {
            $('#login-msg').text('');
            $('#register-msg').text('');
        }

        // 注册表单提交
        $('#register-form').submit(function(e) {
            e.preventDefault();
            let password = $('#reg-password').val();
            let confirm = $('#reg-confirm').val();
            if (password !== confirm) {
                $('#register-msg').text('两次密码不一致').css('color', 'red');
                return;
            }

            $.ajax({
                url: 'register.php',
                type: 'POST',
                data: {
                    username: $('#reg-username').val(),
                    email: $('#reg-email').val(),
                    password: password
                },
                dataType: 'json',
                success: function(response) {
                    if (response.success) {
                        $('#register-msg').text('注册成功,请登录').css('color', 'green');
                        $('#register-form')[0].reset();
                        // 切换到登录标签
                        $('#login-tab').click();
                    } else {
                        $('#register-msg').text(response.message).css('color', 'red');
                    }
                },
                error: function() {
                    $('#register-msg').text('服务器错误').css('color', 'red');
                }
            });
        });

        // 登录表单提交
        $('#login-form').submit(function(e) {
            e.preventDefault();
            $.ajax({
                url: 'login.php',
                type: 'POST',
                data: {
                    username: $('#login-username').val(),
                    password: $('#login-password').val()
                },
                dataType: 'json',
                success: function(response) {
                    if (response.success) {
                        window.location.href = 'dashboard.php';
                    } else {
                        $('#login-msg').text(response.message).css('color', 'red');
                    }
                },
                error: function() {
                    $('#login-msg').text('服务器错误').css('color', 'red');
                }
            });
        });
    });
    </script>
</body>
</html>

3、style.css (页面样式)

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}

.container {
    background: white;
    border-radius: 10px;
    box-shadow: 0 10px 25px rgba(0,0,0,0.1);
    width: 400px;
    max-width: 90%;
}

.form-container {
    padding: 30px;
}

.tabs {
    display: flex;
    margin-bottom: 30px;
    border-bottom: 1px solid #ddd;
}

.tab-btn {
    flex: 1;
    background: none;
    border: none;
    padding: 10px;
    font-size: 16px;
    cursor: pointer;
    color: #666;
    transition: all 0.3s;
    border-bottom: 2px solid transparent;
}

.tab-btn.active {
    color: #667eea;
    border-bottom: 2px solid #667eea;
}

.tab-btn:hover {
    color: #667eea;
}

.form {
    display: none;
}

.form.active {
    display: block;
}

.form h2 {
    margin-bottom: 20px;
    color: #333;
}

.form-group {
    margin-bottom: 20px;
}

.form-group label {
    display: block;
    margin-bottom: 5px;
    color: #555;
    font-weight: 500;
}

.form-group input {
    width: 100%;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
    font-size: 16px;
    transition: border-color 0.3s;
}

.form-group input:focus {
    outline: none;
    border-color: #667eea;
}

.submit-btn {
    width: 100%;
    padding: 12px;
    background: #667eea;
    color: white;
    border: none;
    border-radius: 5px;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.3s;
}

.submit-btn:hover {
    background: #5a67d8;
}

.msg {
    margin-top: 15px;
    text-align: center;
    font-size: 14px;
}

4、config.php (数据库配置)

<?php
// 数据库配置信息
define('DB_HOST', 'localhost');
define('DB_USER', 'root');        // 修改为你的数据库用户名
define('DB_PASS', '');            // 修改为你的数据库密码
define('DB_NAME', 'auth_demo');

// 创建PDO连接
try {
    $dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4';
    $pdo = new PDO($dsn, DB_USER, DB_PASS);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    die('数据库连接失败: ' . $e->getMessage());
}
?>

5、register.php (注册处理)

<?php
header('Content-Type: application/json');
require_once 'config.php';

// 只接受POST请求
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    echo json_encode(['success' => false, 'message' => '非法请求']);
    exit;
}

// 获取并清理输入
$username = trim($_POST['username'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';

// 简单验证
if (empty($username) || empty($email) || empty($password)) {
    echo json_encode(['success' => false, 'message' => '所有字段均为必填']);
    exit;
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo json_encode(['success' => false, 'message' => '邮箱格式不正确']);
    exit;
}

if (strlen($password) < 6) {
    echo json_encode(['success' => false, 'message' => '密码至少6位']);
    exit;
}

try {
    // 检查用户名或邮箱是否已存在
    $stmt = $pdo->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
    $stmt->execute([$username, $email]);
    if ($stmt->fetch()) {
        echo json_encode(['success' => false, 'message' => '用户名或邮箱已存在']);
        exit;
    }

    // 密码哈希
    $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

    // 插入用户
    $stmt = $pdo->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
    $stmt->execute([$username, $email, $hashedPassword]);

    echo json_encode(['success' => true, 'message' => '注册成功']);
} catch (PDOException $e) {
    // 记录错误日志(生产环境应记录到文件)
    error_log($e->getMessage());
    echo json_encode(['success' => false, 'message' => '服务器错误,请稍后重试']);
}
?>

6、login.php (登录处理)

<?php
session_start(); // 启用会话
header('Content-Type: application/json');
require_once 'config.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    echo json_encode(['success' => false, 'message' => '非法请求']);
    exit;
}

$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';

if (empty($username) || empty($password)) {
    echo json_encode(['success' => false, 'message' => '用户名和密码不能为空']);
    exit;
}

try {
    // 查询用户
    $stmt = $pdo->prepare("SELECT id, username, password FROM users WHERE username = ? OR email = ?");
    $stmt->execute([$username, $username]); // 支持使用邮箱登录
    $user = $stmt->fetch();

    if (!$user) {
        echo json_encode(['success' => false, 'message' => '用户名或密码错误']);
        exit;
    }

    // 验证密码
    if (password_verify($password, $user['password'])) {
        // 密码正确,设置session
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];
        echo json_encode(['success' => true, 'message' => '登录成功']);
    } else {
        echo json_encode(['success' => false, 'message' => '用户名或密码错误']);
    }
} catch (PDOException $e) {
    error_log($e->getMessage());
    echo json_encode(['success' => false, 'message' => '服务器错误,请稍后重试']);
}
?>

7、dashboard.php (登录后的欢迎页示例)

<?php
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: index.html');
    exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>欢迎页</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container" style="text-align: center; padding: 40px;">
        <h2>欢迎,<?php echo htmlspecialchars($_SESSION['username']); ?>!</h2>
        <p>你已成功登录。</p>
        <a href="logout.php" class="submit-btn" style="display: inline-block; width: auto; padding: 10px 20px; text-decoration: none;">退出登录</a>
    </div>
</body>
</html>

8、logout.php (退出登录)

<?php
session_start();
session_destroy();
header('Location: index.html');
exit;
?>

9、部署方法:

  1. 创建数据库:在 MySQL 中执行上面的建表语句,创建 auth_demo 库和 users 表。

  2. 配置数据库连接:修改 config.php 中的数据库用户名和密码,确保与你的环境一致。

  3. 放置文件:将所有文件放入 Web 服务器目录(例如 Apache 的 htdocs 下的 project 文件夹)。

  4. 访问页面:将所有文件放入 Web 服务器目录(例如 Apache 的 htdocs 下的 project 文件夹)。

  5. 测试:注册新用户,然后登录。成功后跳转到 dashboard.php。

10、安全配置要点

  • 密码使用 password_hash() 哈希存储,不可逆。

  • 所有数据库操作均使用 PDO 预处理语句,防止 SQL 注入。

  • 输出到 HTML 的内容使用 htmlspecialchars() 转义,防止 XSS 攻击。

  • 登录后使用 Session 维持状态,避免在客户端存储敏感信息。

  • AJAX 请求返回 JSON 格式,便于前端处理。

六、核心功能实现细节

1. 注册模块(register.php)

注册逻辑分五步:

  1. 接收并清理输入:使用 trim() 去除空格。

  2. 基本验证:字段非空、邮箱格式、密码长度。

  3. 唯一性检查:查询用户名/邮箱是否已存在。

  4. 密码哈希:使用 password_hash(),算法选 PASSWORD_DEFAULT(目前是bcrypt)。

  5. 插入数据库:用预处理语句防止SQL注入。

为什么要用预处理语句?如果直接拼接SQL字符串,攻击者可以在用户名里输入 ' OR 1=1; -- 等恶意内容,导致数据泄露或被删。预处理将SQL结构与数据分离,彻底杜绝注入。

为什么密码不能明文存储?
一旦数据库泄露,用户密码直接暴露。用户很可能在其他网站也用相同密码,后果严重。哈希后即使泄露也无法还原原始密码。

七、可以改进的方向

这个项目虽然完整,但离生产级还有距离,你可以尝试扩展:

  • 验证码:防止机器人暴力破解。

  • 邮箱激活:注册后发送激活链接,确保邮箱真实。

  • 记住我:实现自动登录(需处理安全风险)。

  • OAuth登录:集成微信、QQ等第三方登录。

  • 更安全的Session:设置HttpOnly、Secure、SameSite等Cookie属性。

八、总结

写一个登录注册系统,看似简单,实则涉及Web开发的方方面面:前端交互、后端逻辑、数据库设计、安全防护。希望通过这个项目拆解,你能理解每个环节背后的“为什么”。

代码不是终点,思想才是。当你以后用框架时,能想到框架替我们解决了哪些问题,又可能带来哪些新的安全隐患,这才算是真正入门了。

所有源码已附在文中,如果你在实践中有任何问题,欢迎留言交流。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐