PHP 手机号验证码登录

wen PHP项目 5

本文目录导读:

PHP 手机号验证码登录

  1. 数据库设计
  2. 后端PHP代码
  3. 前端演示页面
  4. 优化建议
  5. 使用说明
  6. 注意事项

我来帮你实现一个完整的PHP手机号验证码登录系统,这个系统包含后端API和前端演示页面。

数据库设计

-- 用户表
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `phone` varchar(20) NOT NULL COMMENT '手机号',
  `password` varchar(255) DEFAULT NULL COMMENT '密码(可选)',
  `nickname` varchar(50) DEFAULT NULL,
  `avatar` varchar(255) DEFAULT NULL,
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:1启用 0禁用',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 验证码表
CREATE TABLE `sms_codes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `phone` varchar(20) NOT NULL COMMENT '手机号',
  `code` varchar(6) NOT NULL COMMENT '验证码',
  `expire_time` datetime NOT NULL COMMENT '过期时间',
  `ip` varchar(50) DEFAULT NULL COMMENT 'IP地址',
  `used` tinyint(1) DEFAULT '0' COMMENT '是否已使用',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_phone` (`phone`),
  KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

后端PHP代码

config.php - 配置文件

<?php
// 数据库配置
define('DB_HOST', 'localhost');
define('DB_NAME', 'test');
define('DB_USER', 'root');
define('DB_PASS', '');
// 短信配置(示例使用阿里云短信)
define('ALIYUN_SMS_ACCESS_KEY_ID', 'your_access_key_id');
define('ALIYUN_SMS_ACCESS_KEY_SECRET', 'your_access_key_secret');
define('ALIYUN_SMS_SIGN_NAME', '你的签名');
define('ALIYUN_SMS_TEMPLATE_CODE', 'SMS_123456789'); // 验证码模板
// Session配置
session_start();
// 数据库连接
function db_connect() {
    try {
        $pdo = new PDO(
            "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
            DB_USER,
            DB_PASS
        );
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        return $pdo;
    } catch (PDOException $e) {
        die('数据库连接失败: ' . $e->getMessage());
    }
}
?>

send_sms.php - 发送验证码

<?php
require_once 'config.php';
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    echo json_encode(['code' => 405, 'msg' => '请求方法不允许']);
    exit;
}
$phone = $_POST['phone'] ?? '';
// 验证手机号格式
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
    echo json_encode(['code' => 400, 'msg' => '手机号格式不正确']);
    exit;
}
// 检查发送频率(同一手机号60秒内只能发一次)
$pdo = db_connect();
$stmt = $pdo->prepare("SELECT * FROM sms_codes WHERE phone = ? AND created_at > DATE_SUB(NOW(), INTERVAL 60 SECOND) ORDER BY id DESC LIMIT 1");
$stmt->execute([$phone]);
if ($stmt->fetch()) {
    echo json_encode(['code' => 429, 'msg' => '发送太频繁,请稍后再试']);
    exit;
}
// 检查IP发送频率(同一IP 24小时内最多发10次)
$ip = $_SERVER['REMOTE_ADDR'];
$stmt = $pdo->prepare("SELECT COUNT(*) FROM sms_codes WHERE ip = ? AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)");
$stmt->execute([$ip]);
if ($stmt->fetchColumn() >= 10) {
    echo json_encode(['code' => 429, 'msg' => '今日发送次数已达上限']);
    exit;
}
// 生成6位随机验证码
$code = str_pad(rand(0, 999999), 6, '0', STR_PAD_LEFT);
// 保存验证码到数据库(有效时间5分钟)
$expire_time = date('Y-m-d H:i:s', time() + 300);
$stmt = $pdo->prepare("INSERT INTO sms_codes (phone, code, expire_time, ip) VALUES (?, ?, ?, ?)");
$stmt->execute([$phone, $code, $expire_time, $ip]);
// 发送短信(使用阿里云短信服务)
$result = sendSms($phone, $code);
if ($result['status']) {
    echo json_encode(['code' => 200, 'msg' => '验证码发送成功']);
} else {
    echo json_encode(['code' => 500, 'msg' => '验证码发送失败']);
}
/**
 * 发送短信函数(阿里云短信)
 */
function sendSms($phone, $code) {
    // 这里实现阿里云短信发送
    // 首先需要引入阿里云SDK
    // 为了演示,这里返回成功(实际开发时替换为真实短信服务)
    try {
        // 实际代码示例:
        /*
        $accessKeyId = ALIYUN_SMS_ACCESS_KEY_ID;
        $accessKeySecret = ALIYUN_SMS_ACCESS_KEY_SECRET;
        // 引入阿里云短信SDK
        // require_once 'aliyun-php-sdk/aliyun-php-sdk-core/Config.php';
        // require_once 'aliyun-php-sdk/aliyun-php-sdk-core/Profile/DefaultProfile.php';
        // require_once 'aliyun-php-sdk/aliyun-php-sdk-core/DefaultAcsClient.php';
        // $profile = DefaultProfile::getProfile("cn-hangzhou", $accessKeyId, $accessKeySecret);
        // $client = new DefaultAcsClient($profile);
        // $request = new Dysmsapi\Request\V20170525\SendSmsRequest();
        // $request->setPhoneNumbers($phone);
        // $request->setSignName(ALIYUN_SMS_SIGN_NAME);
        // $request->setTemplateCode(ALIYUN_SMS_TEMPLATE_CODE);
        // $request->setTemplateParam(json_encode(['code' => $code]));
        // $response = $client->getAcsResponse($request);
        // return ['status' => true, 'message' => '发送成功'];
        */
        // 演示环境直接返回成功
        return ['status' => true, 'message' => '模拟发送成功'];
    } catch (Exception $e) {
        return ['status' => false, 'message' => $e->getMessage()];
    }
}
?>

verify_code.php - 验证验证码

<?php
require_once 'config.php';
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    echo json_encode(['code' => 405, 'msg' => '请求方法不允许']);
    exit;
}
$phone = $_POST['phone'] ?? '';
$code = $_POST['code'] ?? '';
// 验证输入
if (empty($phone) || empty($code)) {
    echo json_encode(['code' => 400, 'msg' => '请填写手机号和验证码']);
    exit;
}
$pdo = db_connect();
// 查询最新的验证码
$stmt = $pdo->prepare("
    SELECT * FROM sms_codes 
    WHERE phone = ? AND used = 0 
    ORDER BY id DESC LIMIT 1
");
$stmt->execute([$phone]);
$sms_data = $stmt->fetch(PDO::FETCH_ASSOC);
// 验证验证码
if (!$sms_data) {
    echo json_encode(['code' => 400, 'msg' => '验证码不存在,请重新获取']);
    exit;
}
if (strtotime($sms_data['expire_time']) < time()) {
    echo json_encode(['code' => 400, 'msg' => '验证码已过期,请重新获取']);
    exit;
}
if ($sms_data['code'] !== $code) {
    echo json_encode(['code' => 400, 'msg' => '验证码错误']);
    exit;
}
// 标记验证码已使用
$stmt = $pdo->prepare("UPDATE sms_codes SET used = 1 WHERE id = ?");
$stmt->execute([$sms_data['id']]);
// 检查用户是否存在,不存在则自动注册
$stmt = $pdo->prepare("SELECT * FROM users WHERE phone = ?");
$stmt->execute([$phone]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) {
    // 自动注册新用户
    $stmt = $pdo->prepare("INSERT INTO users (phone, nickname) VALUES (?, ?)");
    $stmt->execute([$phone, '用户' . substr($phone, -4)]);
    $user_id = $pdo->lastInsertId();
} else {
    $user_id = $user['id'];
    // 检查用户状态
    if ($user['status'] == 0) {
        echo json_encode(['code' => 403, 'msg' => '账号已被禁用']);
        exit;
    }
}
// 更新用户登录信息(这里需要token机制)
$token = bin2hex(random_bytes(32));
$_SESSION['user_id'] = $user_id;
$_SESSION['phone'] = $phone;
$_SESSION['token'] = $token;
echo json_encode([
    'code' => 200,
    'msg' => '登录成功',
    'data' => [
        'user_id' => $user_id,
        'phone' => $phone,
        'token' => $token,
        'nickname' => $user['nickname'] ?? '新用户'
    ]
]);
?>

login_api.php - 登录接口(整合版)

<?php
require_once 'config.php';
header('Content-Type: application/json');
$action = $_GET['action'] ?? '';
$method = $_SERVER['REQUEST_METHOD'];
switch ($action) {
    case 'send_code':
        if ($method !== 'POST') {
            echo json_encode(['code' => 405, 'msg' => 'Method Not Allowed']);
            break;
        }
        $phone = $_POST['phone'] ?? '';
        // 验证手机号
        if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
            echo json_encode(['code' => 400, 'msg' => '手机号格式不正确']);
            break;
        }
        // 生成验证码
        $code = str_pad(rand(0, 999999), 6, '0', STR_PAD_LEFT);
        // 检查是否频繁发送
        $pdo = db_connect();
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM sms_codes WHERE phone = ? AND created_at > DATE_SUB(NOW(), INTERVAL 60 SECOND)");
        $stmt->execute([$phone]);
        if ($stmt->fetchColumn() > 0) {
            echo json_encode(['code' => 429, 'msg' => '发送太频繁,请1分钟后再试']);
            break;
        }
        // 保存验证码
        $expire_at = date('Y-m-d H:i:s', time() + 300); // 5分钟有效
        $stmt = $pdo->prepare("INSERT INTO sms_codes (phone, code, expire_time) VALUES (?, ?, ?)");
        $stmt->execute([$phone, $code, $expire_at]);
        // 调用短信服务(此处替换为真实短信服务)
        $sms_sent = sendSmsCode($phone, $code);
        if ($sms_sent) {
            echo json_encode(['code' => 200, 'msg' => '验证码发送成功', 'debug_code' => $code]);
        } else {
            echo json_encode(['code' => 500, 'msg' => '短信发送失败']);
        }
        break;
    case 'login':
        if ($method !== 'POST') {
            echo json_encode(['code' => 405, 'msg' => 'Method Not Allowed']);
            break;
        }
        $phone = $_POST['phone'] ?? '';
        $code = $_POST['code'] ?? '';
        if (empty($phone) || empty($code)) {
            echo json_encode(['code' => 400, 'msg' => '手机号和验证码不能为空']);
            break;
        }
        // 验证验证码(5分钟内有效,且只能使用一次)
        $pdo = db_connect();
        $stmt = $pdo->prepare("
            SELECT * FROM sms_codes 
            WHERE phone = ? AND used = 0 
            AND expire_time > NOW() 
            ORDER BY id DESC LIMIT 1
        ");
        $stmt->execute([$phone]);
        $sms = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$sms || $sms['code'] !== $code) {
            echo json_encode(['code' => 400, 'msg' => '验证码无效或已过期']);
            break;
        }
        // 标记验证码已使用
        $stmt = $pdo->prepare("UPDATE sms_codes SET used = 1 WHERE id = ?");
        $stmt->execute([$sms['id']]);
        // 查找或创建用户
        $stmt = $pdo->prepare("SELECT * FROM users WHERE phone = ?");
        $stmt->execute([$phone]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$user) {
            // 注册新用户
            $stmt = $pdo->prepare("INSERT INTO users (phone, nickname) VALUES (?, ?)");
            $nickname = '用户' . substr($phone, -4);
            $stmt->execute([$phone, $nickname]);
            $userId = $pdo->lastInsertId();
            $user = [
                'id' => $userId,
                'phone' => $phone,
                'nickname' => $nickname
            ];
        }
        // 生成登录token
        $token = bin2hex(random_bytes(32));
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['token'] = $token;
        echo json_encode([
            'code' => 200,
            'msg' => '登录成功',
            'data' => [
                'user_id' => $user['id'],
                'phone' => $user['phone'],
                'nickname' => $user['nickname'],
                'token' => $token
            ]
        ]);
        break;
    case 'check_login':
        // 检查登录状态
        session_start();
        if (isset($_SESSION['user_id'])) {
            echo json_encode([
                'code' => 200,
                'data' => [
                    'user_id' => $_SESSION['user_id'],
                    'phone' => $_SESSION['phone'] ?? ''
                ]
            ]);
        } else {
            echo json_encode(['code' => 401, 'msg' => '未登录']);
        }
        break;
    case 'logout':
        session_start();
        session_destroy();
        echo json_encode(['code' => 200, 'msg' => '退出成功']);
        break;
    default:
        echo json_encode(['code' => 404, 'msg' => '接口不存在']);
}

前端演示页面

login.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">手机号验证码登录</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .login-container {
            background: white;
            border-radius: 12px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            padding: 40px;
            width: 100%;
            max-width: 400px;
        }
        h2 {
            text-align: center;
            margin-bottom: 30px;
            color: #333;
            font-size: 24px;
            font-weight: 500;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 8px;
            color: #555;
            font-size: 14px;
            font-weight: 500;
        }
        input[type="tel"] {
            width: 100%;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 16px;
            transition: border-color 0.3s;
        }
        input[type="tel"]:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
        }
        .input-group {
            display: flex;
            gap: 10px;
        }
        .input-group input {
            flex: 1;
        }
        .btn-send-code {
            background: #f0f0f0;
            border: 1px solid #ddd;
            border-radius: 6px;
            color: #333;
            cursor: pointer;
            font-size: 14px;
            padding: 12px 16px;
            white-space: nowrap;
            transition: all 0.3s;
            min-width: 130px;
        }
        .btn-send-code:hover:not(:disabled) {
            background: #e0e0e0;
        }
        .btn-send-code:disabled {
            color: #999;
            cursor: not-allowed;
            background: #f5f5f5;
        }
        .btn-login {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: none;
            border-radius: 6px;
            color: white;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            transition: opacity 0.3s;
            margin-top: 10px;
        }
        .btn-login:hover:not(:disabled) {
            opacity: 0.9;
        }
        .btn-login:disabled {
            background: #cccccc;
            cursor: not-allowed;
        }
        #message {
            text-align: center;
            margin-top: 15px;
            font-size: 14px;
            min-height: 20px;
        }
        .success {
            color: #4CAF50;
        }
        .error {
            color: #f44336;
        }
        .error-message {
            color: #f44336;
            font-size: 12px;
            margin-top: 5px;
            min-height: 16px;
        }
        .login-info {
            margin-top: 20px;
            padding: 15px;
            background: #f8f9fa;
            border-radius: 6px;
            text-align: center;
            color: #555;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>手机号登录</h2>
        <div class="form-group">
            <label for="phone">手机号</label>
            <input type="tel" id="phone" placeholder="请输入手机号" maxlength="11">
            <div class="error-message" id="phoneError"></div>
        </div>
        <div class="form-group">
            <label for="code">验证码</label>
            <div class="input-group">
                <input type="text" id="code" placeholder="请输入验证码" maxlength="6">
                <button class="btn-send-code" id="sendCodeBtn" onclick="sendCode()">获取验证码</button>
            </div>
            <div class="error-message" id="codeError"></div>
        </div>
        <button class="btn-login" id="loginBtn" onclick="login()">登录</button>
        <div id="message"></div>
        <div class="login-info" id="loginInfo" style="display: none;">
            <p>✅ 登录成功!</p>
            <p id="userInfo"></p>
            <button class="btn-login" style="background: #f44336; margin-top: 10px;" onclick="logout()">退出登录</button>
        </div>
    </div>
    <script>
        let countdown = 0;
        let timer = null;
        // 发送验证码
        async function sendCode() {
            const phone = document.getElementById('phone').value.trim();
            const phoneError = document.getElementById('phoneError');
            const sendBtn = document.getElementById('sendCodeBtn');
            // 清空错误信息
            phoneError.textContent = '';
            // 验证手机号
            if (!phone) {
                phoneError.textContent = '请输入手机号';
                return;
            }
            if (!/^1[3-9]\d{9}$/.test(phone)) {
                phoneError.textContent = '手机号格式不正确';
                return;
            }
            // 禁用按钮
            sendBtn.disabled = true;
            sendBtn.textContent = '发送中...';
            try {
                const response = await fetch('login_api.php?action=send_code', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                    },
                    body: `phone=${phone}`
                });
                const data = await response.json();
                if (data.code === 200) {
                    showMessage('验证码发送成功!', 'success');
                    // 开始倒计时
                    startCountdown(sendBtn);
                    // 开发调试用,实际生产环境不要输出
                    if (data.debug_code) {
                        console.log('开发环境验证码:', data.debug_code);
                        // 自动填入验证码,方便调试
                        document.getElementById('code').value = data.debug_code;
                    }
                } else {
                    showMessage(data.msg || '发送失败', 'error');
                    sendBtn.disabled = false;
                    sendBtn.textContent = '获取验证码';
                }
            } catch (error) {
                showMessage('网络错误,请稍后再试', 'error');
                sendBtn.disabled = false;
                sendBtn.textContent = '获取验证码';
            }
        }
        // 登录
        async function login() {
            const phone = document.getElementById('phone').value.trim();
            const code = document.getElementById('code').value.trim();
            const phoneError = document.getElementById('phoneError');
            const codeError = document.getElementById('codeError');
            const loginBtn = document.getElementById('loginBtn');
            // 清空错误信息
            phoneError.textContent = '';
            codeError.textContent = '';
            // 验证表单
            if (!phone) {
                phoneError.textContent = '请输入手机号';
                return;
            }
            if (!/^1[3-9]\d{9}$/.test(phone)) {
                phoneError.textContent = '手机号格式不正确';
                return;
            }
            if (!code || code.length !== 6) {
                codeError.textContent = '请输入6位验证码';
                return;
            }
            // 禁用登录按钮
            loginBtn.disabled = true;
            loginBtn.textContent = '登录中...';
            try {
                const response = await fetch('login_api.php?action=login', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                    },
                    body: `phone=${phone}&code=${code}`
                });
                const result = await response.json();
                if (result.code === 200) {
                    showMessage('登录成功!', 'success');
                    // 显示登录成功信息
                    document.getElementById('loginInfo').style.display = 'block';
                    document.getElementById('userInfo').textContent = 
                        `用户ID: ${result.data.user_id} | 手机号: ${result.data.phone} | 昵称: ${result.data.nickname}`;
                    // 保存token到localStorage
                    localStorage.setItem('token', result.data.token);
                    // 隐藏登录表单
                    setTimeout(() => {
                        document.querySelector('.form-group').style.display = 'none';
                        document.getElementById('sendCodeBtn').style.display = 'none';
                        document.getElementById('loginBtn').style.display = 'none';
                    }, 500);
                } else {
                    showMessage(result.msg || '登录失败', 'error');
                }
            } catch (error) {
                showMessage('网络错误,请稍后再试', 'error');
            } finally {
                loginBtn.disabled = false;
                loginBtn.textContent = '登录';
            }
        }
        // 退出登录
        function logout() {
            fetch('login_api.php?action=logout')
                .then(response => response.json())
                .then(data => {
                    if (data.code === 200) {
                        localStorage.removeItem('token');
                        location.reload();
                    }
                });
        }
        // 倒计时
        function startCountdown(btn) {
            countdown = 60;
            timer = setInterval(() => {
                countdown--;
                if (countdown <= 0) {
                    clearInterval(timer);
                    btn.disabled = false;
                    btn.textContent = '获取验证码';
                } else {
                    btn.textContent = countdown + 's后重发';
                }
            }, 1000);
        }
        // 显示消息
        function showMessage(msg, type) {
            const messageEl = document.getElementById('message');
            messageEl.textContent = msg;
            messageEl.className = type;
            // 3秒后清除消息
            setTimeout(() => {
                messageEl.textContent = '';
                messageEl.className = '';
            }, 3000);
        }
        // 实时验证手机号格式
        document.getElementById('phone').addEventListener('input', function(e) {
            const phone = e.target.value.replace(/\D/g, '');
            e.target.value = phone;
            if (phone.length === 11 && !/^1[3-9]\d{9}$/.test(phone)) {
                document.getElementById('phoneError').textContent = '手机号格式不正确';
            } else {
                document.getElementById('phoneError').textContent = '';
            }
        });
        // 验证码只能输入数字
        document.getElementById('code').addEventListener('input', function(e) {
            e.target.value = e.target.value.replace(/\D/g, '');
        });
        // 检查登录状态
        window.onload = function() {
            const token = localStorage.getItem('token');
            if (token) {
                fetch('login_api.php?action=check_login')
                    .then(response => response.json())
                    .then(data => {
                        if (data.code === 200 && data.data) {
                            // 自动登录
                            document.getElementById('loginInfo').style.display = 'block';
                            document.getElementById('userInfo').textContent = 
                                `用户ID: ${data.data.user_id} | 手机号: ${data.data.phone}`;
                            document.querySelector('.form-group').style.display = 'none';
                            document.getElementById('sendCodeBtn').style.display = 'none';
                            document.getElementById('loginBtn').style.display = 'none';
                        }
                    });
            }
        };
    </script>
</body>
</html>

优化建议

安全增强 (security.php)

<?php
// 添加安全相关的类和函数
class Security {
    /**
     * 防止SQL注入
     */
    public static function cleanInput($data) {
        return htmlspecialchars(strip_tags(trim($data)));
    }
    /**
     * 生成JWT Token
     */
    public static function generateJWT($user_id, $secret_key) {
        $header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
        $payload = json_encode([
            'user_id' => $user_id,
            'iat' => time(),
            'exp' => time() + 3600 // 1小时有效
        ]);
        $base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
        $base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));
        $signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret_key, true);
        $base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
        return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
    }
    /**
     * 验证JWT Token
     */
    public static function verifyJWT($token, $secret_key) {
        $tokenParts = explode('.', $token);
        if (count($tokenParts) !== 3) return false;
        $header = base64_decode($tokenParts[0]);
        $payload = base64_decode($tokenParts[1]);
        $signature = $tokenParts[2];
        $expectedSignature = hash_hmac('sha256', $tokenParts[0] . "." . $tokenParts[1], $secret_key, true);
        $expectedBase64 = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($expectedSignature));
        if (!hash_equals($expectedBase64, $signature)) {
            return false;
        }
        $payloadData = json_decode($payload, true);
        if ($payloadData['exp'] < time()) {
            return false; // Token过期
        }
        return $payloadData;
    }
}
?>

使用说明

  1. 配置数据库:根据实际情况修改config.php中的数据库连接信息
  2. 配置短信服务:如果你有阿里云/腾讯云短信服务,在send_sms.php中配置
  3. 部署到服务器:将文件部署到支持PHP的服务器
  4. 测试流程
    • 打开login.html
    • 输入手机号,点击获取验证码
    • 查看数据库中sms_codes表的验证码(或查看控制台输出的调试验证码)
    • 输入验证码登录

注意事项

  1. 生产环境:一定要禁用debug_code输出
  2. 邮件验证:可以考虑添加邮件验证码功能
  3. 验证码存储:生产环境建议使用Redis等缓存数据库
  4. Rate Limit:生产环境建议增加更完善的限流机制
  5. HTTPS:生产环境必须使用HTTPS加密传输
  6. 日志记录:建议记录所有登录操作日志

这个系统已经够用于学习或中小型项目,如果是在生产环境使用,建议进一步增强安全性。

抱歉,评论功能暂时关闭!