輕量級專案(如行動 App 後端、內部管理系統)常需快速建立登入 API。本文以新浪雲(SAE)平台為例,示範 SaeMysql 連線、參數接收、密碼驗證、JSON 統一回應格式,並補充通用 PHP(PDO/MySQLi)寫法與關鍵安全強化點。
<?php
// login.php - SAE 版
header('Content-Type: application/json; charset=utf-8');
// 1. 統一回應格式
function jsonResponse(bool $success, string $message = '', array $data = [], int $code = 200) {
http_response_code($code);
echo json_encode(['success' => $success, 'message' => $message, 'data' => $data], JSON_UNESCAPED_UNICODE);
exit;
}
// 2. 參數接收(支援 GET/POST/JSON)
$input = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if (strpos($contentType, 'application/json') === 0) {
$input = json_decode(file_get_contents('php://input'), true) ?? [];
} else {
$input = $_POST;
}
} else {
$input = $_GET;
}
$username = trim($input['username'] ?? '');
$password = $input['password'] ?? '';
if ($username === '' || $password === '') {
jsonResponse(false, '帳號密碼不可為空', [], 400);
}
// 3. SAE 專用資料庫連線
try {
$mysql = new SaeMysql(); // SAE 內建,無需 host/user/pass
// 防注入:使用預處理(SaeMysql 支援 prepare/execute)
$stmt = $mysql->prepare('SELECT id, username, password_hash, status FROM admin_users WHERE username = ?');
$stmt->bind_param('s', $username);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
$stmt->close();
} catch (Exception $e) {
error_log('DB Error: ' . $e->getMessage());
jsonResponse(false, '系統錯誤,請稍後再試', [], 500);
}
// 4. 驗證
if (!$user) {
jsonResponse(false, '帳號不存在', [], 401);
}
if ($user['status'] !== 1) {
jsonResponse(false, '帳號已停用', [], 403);
}
// 密碼驗證:假設資料庫存 password_hash() 雜湊
if (!password_verify($password, $user['password_hash'])) {
jsonResponse(false, '密碼錯誤', [], 401);
}
// 5. 登入成功:建立 Session/Token
session_start();
$_SESSION['admin_id'] = $user['id'];
$_SESSION['admin_user'] = $user['username'];
session_regenerate_id(true); // 防 Session Fixation
jsonResponse(true, '登入成功', [
'user_id' => $user['id'],
'username' => $user['username'],
'token' => session_id(), // 簡易示範,實際建議 JWT
]);
<?php
// 通用 PDO 版本(適用所有環境)
$dsn = 'mysql:host=localhost;dbname=myapp;charset=utf8mb4';
$pdo = new PDO($dsn, 'db_user', 'db_pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$stmt = $pdo->prepare('SELECT id, username, password_hash, status FROM admin_users WHERE username = ?');
$stmt->execute([$username]);
$user = $stmt->fetch();
// 後續驗證邏輯相同
CREATE TABLE admin_users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL, -- 存 password_hash() 結果
status TINYINT DEFAULT 1, -- 1=正常, 0=停用
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 建立測試帳號(密碼:admin123)
INSERT INTO admin_users (username, password_hash) VALUES
('admin', '$2y$10$AbCdEfGhIjKlMnOpQrStUvWxYz1234567890AbCdEfGhIjKlMnOpQr'); -- password_hash('admin123', PASSWORD_DEFAULT)
| 項目 | 做法 | 風險若不做 |
|---|---|---|
| 密碼儲存 | password_hash() + password_verify(),禁用 MD5/SHA1/明文 | 資料庫洩露即全盤帳號失陷 |
| SQL 注入 | 全站預處理(PDO/SaeMysql prepare),拒絕字串拼接 | 繞過登入、資料竊取/刪除 |
| 暴力破解 | 失敗計數 + 延遲/鎖定 + 驗證碼 + IP 限流 | 短時間嘗試大量密碼 |
| Session 安全 | httponly、secure、regenerate_id、Redis 分散式儲存 | XSS 竊取 Session、Session Fixation |
| 傳輸加密 | 全站 HTTPS,session.cookie_secure=1 | 中間人竊取帳密/Session |
| 錯誤資訊 | 生產環境不輸出 SQL/堆疊,統一代碼回應 | 洩露資料庫結構、路徑 |
| CORS | 明確 Access-Control-Allow-Origin,不可 * | 跨站請求偽造 (CSRF) |
<?php
// 登入成功後簽發 JWT(需 firebase/php-jwt)
use Firebase\JWT\JWT;
$payload = [
'iss' => 'myapp',
'sub' => $user['id'],
'iat' => time(),
'exp' => time() + 3600, // 1 小時
'username' => $user['username'],
];
$jwt = JWT::encode($payload, 'your_secret_key', 'HS256');
jsonResponse(true, '登入成功', ['token' => $jwt, 'expires_in' => 3600]);
// 後續 API 驗證中介軟體
function authMiddleware(): ?array {
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (preg_match('/Bearer\s+(.+)/', $auth, $m)) {
try {
return JWT::decode($m[1], 'your_secret_key', ['HS256']);
} catch (Exception $e) { return null; }
}
return null;
}
總結: 簡易登入核心在於「參數接收 → 預處理查詢 → password_verify → Session/JWT 簽發 → JSON 回應」。SAE 環境用 SaeMysql,通用環境用 PDO。務必落實密碼雜湊、預處理、HTTPS、錯誤碼規範,才算合格的生產級登入 API。