PHP 로그인 유지 안되는 문제 해결하기

프리터코더·2025년 5월 25일
0

php 문제 해결

목록 보기
26/79

세션이나 쿠키를 통한 로그인 상태가 유지되지 않는 문제의 원인과 해결 방법을 알아보겠습니다.

주요 증상

로그인 후 페이지 이동 시 로그아웃 상태
세션 데이터가 사라짐
"로그인이 필요합니다" 메시지 반복 출현

원인 분석

  1. 세션 설정 문제: 세션 시작 누락, 잘못된 설정
  2. 쿠키 설정 오류: 도메인, 경로, 보안 설정 문제
  3. 세션 저장소 문제: 권한, 디스크 공간 부족
  4. 브라우저 정책: SameSite, Secure 속성 문제

해결책

1. 기본 세션 관리 클래스

<?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'] ?? [];
    }
}
?>

2. Remember Me 기능 구현

<?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)]);
    }
}
?>

3. 로그인 처리 예시

<?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;
}
?>

4. 인증 미들웨어

<?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;
        }
    }
}
?>

5. 세션 디버깅 도구

<?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();
?>

6. 보안 강화 설정

<?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);
}
?>

예방법

  • 적절한 세션 설정 및 보안 강화
  • Remember Me 기능으로 사용자 편의성 제공
  • 세션 만료 시간 적절히 설정
  • 정기적인 세션 정리 및 모니터링
  • HTTPS 사용 권장

로그인 유지 문제는 대부분 세션 설정이나 쿠키 정책 문제로 발생하므로, 체계적인 세션 관리가 핵심입니다.

profile
일용직 개발자. freetercoder@gmail.com

0개의 댓글