세션이나 쿠키를 통한 로그인 상태가 유지되지 않는 문제의 원인과 해결 방법을 알아보겠습니다.
로그인 후 페이지 이동 시 로그아웃 상태
세션 데이터가 사라짐
"로그인이 필요합니다" 메시지 반복 출현
<?php
class SessionManager {
private static $started = false;
public static function start() {
if (self::$started) {
return;
}
// 세션 설정
ini_set('session.cookie_lifetime', 86400); // 24시간
ini_set('session.cookie_httponly', 1);
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'Lax');
// HTTPS 환경에서만 Secure 설정
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
ini_set('session.cookie_secure', 1);
}
session_start();
self::$started = true;
// 세션 보안 강화
self::regenerateIfNeeded();
}
public static function login($userId, $userData = []) {
self::start();
// 세션 재생성 (세션 하이재킹 방지)
session_regenerate_id(true);
$_SESSION['user_id'] = $userId;
$_SESSION['user_data'] = $userData;
$_SESSION['login_time'] = time();
$_SESSION['last_activity'] = time();
return true;
}
public static function logout() {
self::start();
// 세션 데이터 삭제
$_SESSION = [];
// 세션 쿠키 삭제
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 3600, '/');
}
// 세션 파괴
session_destroy();
self::$started = false;
}
public static function isLoggedIn() {
self::start();
if (!isset($_SESSION['user_id'])) {
return false;
}
// 세션 만료 체크
if (self::isExpired()) {
self::logout();
return false;
}
// 활동 시간 업데이트
$_SESSION['last_activity'] = time();
return true;
}
private static function isExpired() {
$maxLifetime = 86400; // 24시간
if (isset($_SESSION['last_activity'])) {
return (time() - $_SESSION['last_activity']) > $maxLifetime;
}
return true;
}
private static function regenerateIfNeeded() {
$regenerateInterval = 1800; // 30분
if (!isset($_SESSION['last_regeneration'])) {
$_SESSION['last_regeneration'] = time();
} elseif (time() - $_SESSION['last_regeneration'] > $regenerateInterval) {
session_regenerate_id(true);
$_SESSION['last_regeneration'] = time();
}
}
public static function getUserId() {
return $_SESSION['user_id'] ?? null;
}
public static function getUserData() {
return $_SESSION['user_data'] ?? [];
}
}
?>
<?php
class RememberMe {
private static $cookieName = 'remember_token';
private static $cookieLifetime = 2592000; // 30일
public static function setRememberToken($userId) {
// 랜덤 토큰 생성
$token = bin2hex(random_bytes(32));
$expiry = time() + self::$cookieLifetime;
// 데이터베이스에 토큰 저장
self::saveTokenToDatabase($userId, $token, $expiry);
// 쿠키 설정
$secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
setcookie(
self::$cookieName,
$token,
[
'expires' => $expiry,
'path' => '/',
'domain' => '',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax'
]
);
}
public static function checkRememberToken() {
if (!isset($_COOKIE[self::$cookieName])) {
return false;
}
$token = $_COOKIE[self::$cookieName];
$userData = self::validateToken($token);
if ($userData) {
// 자동 로그인
SessionManager::login($userData['user_id'], $userData);
// 토큰 갱신
self::setRememberToken($userData['user_id']);
return true;
}
// 유효하지 않은 토큰 삭제
self::clearRememberToken();
return false;
}
public static function clearRememberToken() {
if (isset($_COOKIE[self::$cookieName])) {
$token = $_COOKIE[self::$cookieName];
self::removeTokenFromDatabase($token);
setcookie(self::$cookieName, '', time() - 3600, '/');
}
}
private static function saveTokenToDatabase($userId, $token, $expiry) {
// 데이터베이스 연결 (PDO 예시)
$pdo = Database::getConnection();
$stmt = $pdo->prepare("
INSERT INTO remember_tokens (user_id, token, expires_at, created_at)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
token = VALUES(token),
expires_at = VALUES(expires_at)
");
$stmt->execute([$userId, hash('sha256', $token), date('Y-m-d H:i:s', $expiry)]);
}
private static function validateToken($token) {
$pdo = Database::getConnection();
$stmt = $pdo->prepare("
SELECT u.id, u.username, u.email
FROM users u
JOIN remember_tokens rt ON u.id = rt.user_id
WHERE rt.token = ? AND rt.expires_at > NOW()
");
$stmt->execute([hash('sha256', $token)]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
private static function removeTokenFromDatabase($token) {
$pdo = Database::getConnection();
$stmt = $pdo->prepare("DELETE FROM remember_tokens WHERE token = ?");
$stmt->execute([hash('sha256', $token)]);
}
}
?>
<?php
require_once 'src/session_manager.php';
require_once 'src/remember_me.php';
// 자동 로그인 체크
if (!SessionManager::isLoggedIn()) {
RememberMe::checkRememberToken();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
try {
// 사용자 인증
$user = authenticateUser($username, $password);
if ($user) {
// 로그인 처리
SessionManager::login($user['id'], [
'username' => $user['username'],
'email' => $user['email']
]);
// Remember Me 처리
if ($remember) {
RememberMe::setRememberToken($user['id']);
}
// 리다이렉트
$redirectUrl = $_SESSION['redirect_after_login'] ?? '/dashboard.php';
unset($_SESSION['redirect_after_login']);
header("Location: $redirectUrl");
exit;
} else {
$error = '아이디 또는 비밀번호가 올바르지 않습니다.';
}
} catch (Exception $e) {
$error = '로그인 처리 중 오류가 발생했습니다.';
error_log('Login error: ' . $e->getMessage());
}
}
function authenticateUser($username, $password) {
$pdo = Database::getConnection();
$stmt = $pdo->prepare("SELECT id, username, email, password FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
unset($user['password']); // 비밀번호 제거
return $user;
}
return false;
}
?>
<?php
class AuthMiddleware {
public static function requireLogin() {
SessionManager::start();
// 자동 로그인 체크
if (!SessionManager::isLoggedIn()) {
RememberMe::checkRememberToken();
}
// 여전히 로그인되지 않은 경우
if (!SessionManager::isLoggedIn()) {
// 현재 URL 저장 (로그인 후 리다이렉트용)
$_SESSION['redirect_after_login'] = $_SERVER['REQUEST_URI'];
header('Location: /auth/login.php');
exit;
}
}
public static function requireGuest() {
SessionManager::start();
if (SessionManager::isLoggedIn()) {
header('Location: /dashboard.php');
exit;
}
}
}
?>
<?php
class SessionDebugger {
public static function debugSession() {
echo "=== 세션 디버깅 정보 ===\n";
echo "세션 ID: " . session_id() . "\n";
echo "세션 상태: " . session_status() . "\n";
echo "세션 저장 경로: " . session_save_path() . "\n";
echo "세션 이름: " . session_name() . "\n";
echo "\n=== 세션 설정 ===\n";
echo "cookie_lifetime: " . ini_get('session.cookie_lifetime') . "\n";
echo "cookie_httponly: " . ini_get('session.cookie_httponly') . "\n";
echo "cookie_secure: " . ini_get('session.cookie_secure') . "\n";
echo "cookie_samesite: " . ini_get('session.cookie_samesite') . "\n";
echo "\n=== 세션 데이터 ===\n";
var_dump($_SESSION);
echo "\n=== 쿠키 정보 ===\n";
var_dump($_COOKIE);
// 세션 파일 확인
$sessionFile = session_save_path() . '/sess_' . session_id();
if (file_exists($sessionFile)) {
echo "\n=== 세션 파일 내용 ===\n";
echo file_get_contents($sessionFile) . "\n";
}
}
}
// 사용법
SessionManager::start();
SessionDebugger::debugSession();
?>
<?php
// php.ini 또는 코드에서 설정
ini_set('session.cookie_httponly', 1); // XSS 방지
ini_set('session.use_strict_mode', 1); // 세션 하이재킹 방지
ini_set('session.cookie_samesite', 'Lax'); // CSRF 방지
ini_set('session.use_only_cookies', 1); // URL 세션 ID 방지
ini_set('session.entropy_length', 32); // 강력한 세션 ID
ini_set('session.hash_function', 'sha256'); // 해시 함수 강화
// HTTPS 환경에서만
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
ini_set('session.cookie_secure', 1);
}
?>
로그인 유지 문제는 대부분 세션 설정이나 쿠키 정책 문제로 발생하므로, 체계적인 세션 관리가 핵심입니다.