轻量级 PHP 登录注册系统源码|SQLite + 密码哈希 + CSRF(原创教学)

轻量级 PHP 登录注册系统源码|SQLite + 密码哈希 + CSRF(原创教学)

前言

做会员站、资源站最常用到的就是登录 / 注册 / 会话。今天分享一套原创教学用途的轻量 PHP 账号系统源码:SQLite 存储、password_hash 密码哈希、CSRF 防护、Session 会话,单目录可部署,适合学习鉴权基础或接到自己的项目里。

声明:本文源码为原创教学示例,请勿用于违法违规用途;请遵守当地法律法规与目标服务条款。

功能特性

  • 注册:用户名规则校验、邮箱唯一、密码强度
  • 登录:用户名或邮箱 + 密码,失败提示统一
  • 会话:登录后 session_regenerate_id 防会话固定
  • 安全:PDO 预处理防注入、输出转义防 XSS、CSRF Token
  • SQLite 免安装,首次运行自动建库并写入演示账号
  • 深色简洁 UI,手机可用

环境要求

  • PHP 8.0+(需 pdo_sqlite)
  • 任意可跑 PHP 的主机 / 宝塔 / Nginx+php-fpm

目录结构

light-auth/
├── index.php    # 路由与页面
├── lib.php      # 认证核心
├── config.php   # 配置
└── data/        # SQLite(自动创建)
    └── users.db

完整源码

config.php

<?php
// 轻量登录注册系统 · 配置
return [
    'site_name'  => '轻账号中心',
    'db_path'    => __DIR__ . '/data/users.db',
    'allow_reg'  => true,
    'session_name' => 'LIGHTAUTH',
    'min_pass_len' => 8,
];

lib.php

<?php
/**
 * 轻量认证核心:SQLite + Session + CSRF + 密码哈希
 */
function cfg(string $k, $d = null) {
    static $c = null;
    if ($c === null) $c = require __DIR__ . '/../config.php';
    return $c[$k] ?? $d;
}

function db(): PDO {
    static $pdo = null;
    if ($pdo instanceof PDO) return $pdo;
    $path = cfg('db_path');
    if (!is_dir(dirname($path))) mkdir(dirname($path), 0755, true);
    $isNew = !file_exists($path);
    $pdo = new PDO('sqlite:' . $path);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    $pdo->exec("CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT UNIQUE NOT NULL,
        email TEXT UNIQUE NOT NULL,
        password_hash TEXT NOT NULL,
        created_at TEXT DEFAULT CURRENT_TIMESTAMP
    )");
    if ($isNew) {
        $pdo->prepare("INSERT INTO users (username, email, password_hash) VALUES (?,?,?)")
            ->execute(['admin', 'admin@example.com', password_hash('admin12345', PASSWORD_DEFAULT)]);
    }
    return $pdo;
}

function e(?string $s): string {
    return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function csrf(): string {
    if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(16));
    return $_SESSION['csrf'];
}

function csrf_ok(): bool {
    return hash_equals(csrf(), (string)($_POST['csrf'] ?? ''));
}

function flash(string $k, ?string $v = null) {
    if ($v !== null) { $_SESSION['flash'][$k] = $v; return null; }
    $x = $_SESSION['flash'][$k] ?? null;
    unset($_SESSION['flash'][$k]);
    return $x;
}

function login_user(array $user): void {
    session_regenerate_id(true);
    $_SESSION['uid'] = (int)$user['id'];
    $_SESSION['uname'] = $user['username'];
}

function current_user(): ?array {
    if (empty($_SESSION['uid'])) return null;
    $st = db()->prepare('SELECT id, username, email, created_at FROM users WHERE id = ?');
    $st->execute([$_SESSION['uid']]);
    return $st->fetch() ?: null;
}

function require_login(): void {
    if (!current_user()) {
        header('Location: index.php?r=login');
        exit;
    }
}

function register_user(string $username, string $email, string $password): array {
    $username = trim($username);
    $email = trim($email);
    if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
        return ['ok' => false, 'msg' => '用户名需为 3-20 位字母数字下划线'];
    }
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return ['ok' => false, 'msg' => '邮箱格式不正确'];
    }
    if (strlen($password) < (int)cfg('min_pass_len', 8)) {
        return ['ok' => false, 'msg' => '密码至少 ' . cfg('min_pass_len') . ' 位'];
    }
    $st = db()->prepare('SELECT 1 FROM users WHERE username = ? OR email = ?');
    $st->execute([$username, $email]);
    if ($st->fetch()) {
        return ['ok' => false, 'msg' => '用户名或邮箱已存在'];
    }
    db()->prepare('INSERT INTO users (username, email, password_hash) VALUES (?,?,?)')
        ->execute([$username, $email, password_hash($password, PASSWORD_DEFAULT)]);
    $st = db()->prepare('SELECT * FROM users WHERE username = ?');
    $st->execute([$username]);
    login_user($st->fetch());
    return ['ok' => true, 'msg' => '注册成功'];
}

function login_by_password(string $account, string $password): array {
    $st = db()->prepare('SELECT * FROM users WHERE username = ? OR email = ?');
    $st->execute([trim($account), trim($account)]);
    $u = $st->fetch();
    if (!$u || !password_verify($password, $u['password_hash'])) {
        return ['ok' => false, 'msg' => '账号或密码错误'];
    }
    login_user($u);
    return ['ok' => true, 'msg' => '登录成功'];
}

index.php

<?php
declare(strict_types=1);
session_name(cfg('session_name'));
session_start();
require __DIR__ . '/lib.php';

$r = $_GET['r'] ?? 'home';
header('Content-Type: text/html; charset=utf-8');

// 处理 POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!csrf_ok()) {
        die('CSRF 校验失败');
    }
    if ($r === 'register' && cfg('allow_reg')) {
        $res = register_user($_POST['username'] ?? '', $_POST['email'] ?? '', $_POST['password'] ?? '');
        flash('msg', $res['msg']);
        header('Location: index.php?r=' . ($res['ok'] ? 'home' : 'register'));
        exit;
    }
    if ($r === 'login') {
        $res = login_by_password($_POST['account'] ?? '', $_POST['password'] ?? '');
        flash('msg', $res['msg']);
        header('Location: index.php?r=' . ($res['ok'] ? 'home' : 'login'));
        exit;
    }
    if ($r === 'logout') {
        session_destroy();
        header('Location: index.php');
        exit;
    }
}

$me = current_user();
$title = cfg('site_name');

function layout_top(string $title, ?array $me): void {
    echo '<!doctype html><html lang="zh-CN"><head><meta charset="utf-8">';
    echo '<meta name="viewport" content="width=device-width,initial-scale=1">';
    echo '<title>' . htmlspecialchars($title) . '</title><style>
    body{font-family:system-ui,-apple-system,"PingFang SC",sans-serif;margin:0;background:#0f1115;color:#e8eaed}
    .nav{height:60px;display:flex;align-items:center;gap:16px;padding:0 20px;border-bottom:1px solid #2a2f3a;background:#1a1d24}
    .nav a{color:#b0b7c4;text-decoration:none;padding:8px 12px;border-radius:8px}
    .nav a:hover{color:#f04494;background:rgba(240,68,148,.12)}
    .nav .brand{font-weight:700;color:#fff}
    .wrap{max-width:420px;margin:48px auto;padding:0 16px}
    .card{background:#1a1d24;border:1px solid #2a2f3a;border-radius:16px;padding:28px}
    h1{margin:0 0 8px;font-size:22px}
    label{display:block;font-size:13px;color:#8b93a7;margin:12px 0 6px}
    input{width:100%;box-sizing:border-box;padding:11px 14px;border-radius:10px;border:1px solid #2a2f3a;background:#12151c;color:#fff}
    input:focus{outline:0;border-color:#f04494}
    button{width:100%;margin-top:18px;padding:12px;border:0;border-radius:999px;background:#f04494;color:#fff;font-weight:600;cursor:pointer}
    .msg{padding:10px 12px;border-radius:10px;background:rgba(240,68,148,.12);color:#ff8fbc;font-size:14px;margin-bottom:12px}
    .ok{background:rgba(61,214,140,.12);color:#7dffa3}
    .muted{color:#8b93a7;font-size:13px;text-align:center;margin-top:16px}
    .muted a{color:#f04494}
    </style></head><body>
    <nav class="nav"><span class="brand">' . htmlspecialchars(cfg('site_name')) . '</span>
    <a href="index.php">首页</a>';
    if ($me) {
        echo '<span style="flex:1"></span><a href="index.php?r=profile">我的资料</a>';
        echo '<form method="post" action="index.php?r=logout" style="display:inline">';
        echo '<input type="hidden" name="csrf" value="' . htmlspecialchars(csrf()) . '">';
        echo '<button type="submit" style="width:auto;padding:8px 14px;margin:0">退出</button></form>';
    } else {
        echo '<span style="flex:1"></span><a href="index.php?r=login">登录</a><a href="index.php?r=register">注册</a>';
    }
    echo '</nav><div class="wrap">';
}

function layout_bot(): void {
    echo '</div></body></html>';
}

layout_top($title, $me);
$msg = flash('msg');

switch ($r) {
    case 'login':
        if ($me) { header('Location: index.php'); exit; }
        if ($msg) echo '<div class="msg">' . e($msg) . '</div>';
        echo '<div class="card"><h1>登录</h1>
        <form method="post" action="index.php?r=login">
          <input type="hidden" name="csrf" value="' . e(csrf()) . '">
          <label>用户名 / 邮箱</label><input name="account" required autofocus>
          <label>密码</label><input type="password" name="password" required>
          <button type="submit">登录</button>
        </form>
        <div class="muted">没有账号?<a href="index.php?r=register">去注册</a></div></div>';
        break;

    case 'register':
        if ($me) { header('Location: index.php'); exit; }
        if (!cfg('allow_reg')) { echo '<div class="msg">注册已关闭</div>'; break; }
        if ($msg) echo '<div class="msg">' . e($msg) . '</div>';
        echo '<div class="card"><h1>注册</h1>
        <form method="post" action="index.php?r=register">
          <input type="hidden" name="csrf" value="' . e(csrf()) . '">
          <label>用户名</label><input name="username" required placeholder="3-20 位字母数字下划线">
          <label>邮箱</label><input type="email" name="email" required>
          <label>密码</label><input type="password" name="password" required minlength="8">
          <button type="submit">创建账号</button>
        </form>
        <div class="muted">已有账号?<a href="index.php?r=login">去登录</a></div></div>';
        break;

    case 'profile':
        require_login();
        if ($msg) echo '<div class="msg ok">' . e($msg) . '</div>';
        echo '<div class="card"><h1>我的资料</h1>
        <p>ID:' . (int)$me['id'] . '</p>
        <p>用户名:' . e($me['username']) . '</p>
        <p>邮箱:' . e($me['email']) . '</p>
        <p>注册时间:' . e($me['created_at']) . '</p>
        <div class="muted"><a href="index.php">返回首页</a></div></div>';
        break;

    default:
        if ($msg) echo '<div class="msg">' . e($msg) . '</div>';
        echo '<div class="card"><h1>' . e(cfg('site_name')) . '</h1>';
        if ($me) {
            echo '<p>你好,<strong>' . e($me['username']) . '</strong>,已登录。</p>';
            echo '<p class="muted" style="text-align:left"><a href="index.php?r=profile">查看资料</a></p>';
        } else {
            echo '<p>轻量 PHP 登录注册演示:SQLite 存储、密码哈希、CSRF 防护。</p>';
            echo '<p><a href="index.php?r=login"><button type="button" style="width:auto;padding:10px 18px;margin:0">登录</button></a> ';
            echo '<a href="index.php?r=register"><button type="button" style="width:auto;padding:10px 18px;margin:0;background:#2a2f3a">注册</button></a></p>';
        }
        echo '</div>';
}
layout_bot();

? 源码下载

下载 light-auth.zip(解压即用)

下载解压后按文中步骤部署即可。若正文代码与压缩包不一致,以压缩包为准。

部署步骤

  1. 新建站点,上传 index.php、lib.php、config.php,创建 data/ 目录并给 PHP 写权限。
  2. 修改 config.php 站点名;把演示账号改成你自己的或删除注册行。
  3. 浏览器打开首页,注册或使用演示账号 admin / admin12345 登录(请立刻改掉)。
  4. 生产环境请关闭错误显示、启用 HTTPS,并限制 data/ 目录 Web 访问。

安全与合规说明

  • 密码使用 password_hash / password_verify,不要用 MD5。
  • 请修改演示密码;登录接口可自行加验证码或限流防爆破。
  • 禁止用于未授权撞库、非法注册机等场景。
  • 本源码仅供学习交流,使用后果自负。

小结

几十行核心代码就能实现可用的登录注册。下一步可扩展:找回密码、第三方登录、记住我(Remember Me)、RBAC 权限。需要增强版可在评论区留言。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容