Menu

PHP 세션과 쿠키: session_start와 setcookie

모든 페이지 맨 위에서 session_start()를 호출한 다음 $_SESSION['key']에 값을 저장하세요. PHP는 그것을 서버에 보관하고 쿠키로 방문자를 기억합니다. setcookie('name', 'value', $options)는 작은 값을 브라우저에 저장하며, $_COOKIE로 다시 읽습니다. 로그인, 로그아웃, 플래시 메시지, 쿠키 옵션, 보안 설정을 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

페이지 맨 위에서 session_start()를 호출한 다음, $_SESSION['key']를 다른 배열처럼 읽고 쓰세요. PHP는 그것을 서버에 저장하고 방문자의 다음 요청에서 다시 찾습니다. 브라우저가 보관해야 할 작은 값이라면 setcookie('name', 'value', time() + 86400)를 호출하고, 이후 요청에서 $_COOKIE['name']으로 다시 읽으세요.

<?php
session_start();                        // before any output
setcookie('theme', 'dark', time() + 60 * 60 * 24 * 30);   // 30 days, also before output

$_SESSION['visits'] = ($_SESSION['visits'] ?? 0) + 1;
$theme = $_COOKIE['theme'] ?? 'light';  // set on an earlier visit
echo "You have opened this page {$_SESSION['visits']} times.";

세션과 쿠키는 브라우저의 여러 HTTP 요청에 걸쳐서만 의미가 있으므로, 그것이 필요한 예제는 일반 코드입니다. 그 주변의 로직은 배열에 대한 평범한 PHP이며, 그 부분은 여기서 실행됩니다. 실행 가능한 각 블록에서 $session은 $_SESSION을 대신하고, 각 호출이 하나의 요청입니다.

PHP 세션의 동작 방식

  1. 첫 session_start()는 무작위 id를 만들어 PHPSESSID라는 쿠키로 브라우저에 보내고, 빈 $_SESSION을 줍니다.
  2. 요청이 끝나면 PHP는 $_SESSION을 서버에 저장합니다(기본은 파일, 또는 Redis나 데이터베이스).
  3. 다음 요청에서 브라우저가 쿠키를 다시 보내면 session_start()가 id를 읽고 저장된 배열을 불러옵니다.

브라우저는 데이터를 보지 못하고 id만 봅니다. 그래서 로그인한 사용자의 id는 직접 만든 쿠키가 아니라 $_SESSION에 넣어야 합니다. 방문자는 쿠키를 어떤 값으로든 바꿀 수 있지만 $_SESSION은 바꿀 수 없습니다.

세션으로 로그인과 로그아웃하기

로그인은 비밀번호 확인, 세션 id 재생성, 사용자 id 저장입니다. 로그아웃은 그 반대이며 세 단계가 있습니다.

<?php
// login.php
session_start();

$user = find_user_by_email($_POST['email'] ?? '');
if ($user && password_verify($_POST['password'] ?? '', $user['password_hash'])) {
    session_regenerate_id(true);        // new id after login: stops session fixation
    $_SESSION['user_id'] = $user['id'];
    header('Location: /dashboard.php');
    exit;
}
$error = 'Wrong email or password.';
<?php
// any protected page
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: /login.php');
    exit;
}
<?php
// logout.php
session_start();
$_SESSION = [];                                     // 1. forget the data

$p = session_get_cookie_params();                   // 2. delete the cookie
setcookie(session_name(), '', time() - 3600, $p['path'], $p['domain'], $p['secure'], $p['httponly']);

session_destroy();                                  // 3. delete the stored session
header('Location: /');
exit;

session_regenerate_id(true)는 보기보다 중요합니다. 공격자가 피해자에게 공격자가 아는 세션 id를 쓰게 만들 수 있고(예: 링크를 통해) 로그인 후에도 id가 그대로라면, 공격자도 로그인된 상태가 됩니다. 로그인 시 새 id를 만들면 그 구멍이 막힙니다. 비밀번호 확인 자체는 password_hash 페이지에서 다룹니다.

활동이 없으면 세션 만료하기

PHP 자체의 정리(session.gc_maxlifetime, 기본 1440초)는 무작위로 실행되므로 믿을 만한 시간 제한이 아닙니다. 마지막 요청 시간을 저장하고 직접 확인하세요. 블록은 각 경우를 볼 수 있도록 고정된 타임스탬프를 씁니다. $timeout이나 시간을 바꿔 보세요.

09:05부터 09:16:40까지의 간격은 700초로 15분 제한보다 짧으므로 사용자는 로그인 상태로 남습니다. 다음 간격은 1000초이므로 세션이 지워집니다.

플래시 메시지

플래시 메시지는 한 요청에서 설정하고 다음 요청에서 한 번 보여 주는 것이며, 대표적으로 리다이렉트 후의 "Saved."입니다. 세션에 저장한 다음 한 번에 읽고 제거하세요.

setcookie로 쿠키 설정하기

setcookie()는 이름, 값, 옵션 배열(PHP 7.3 이상)을 받습니다. 이유가 없다면 모든 쿠키에 이 네 가지 옵션을 설정하세요.

<?php
setcookie('theme', 'dark', [
    'expires'  => time() + 60 * 60 * 24 * 30,   // 30 days; 0 means "until the browser closes"
    'path'     => '/',                          // send it on every page, not just this folder
    'secure'   => true,                         // only over HTTPS
    'httponly' => true,                         // JavaScript cannot read it
    'samesite' => 'Lax',                        // not sent on most cross-site requests
]);

$theme = $_COOKIE['theme'] ?? 'light';

사람들이 걸려 넘어지는 것이 두 가지 있습니다.

  • $_COOKIE는 같은 요청 안에서 바뀌지 않습니다. 이번 요청과 함께 브라우저가 보낸 것을 담습니다. 지금 설정한 쿠키는 다음 요청의 $_COOKIE에 나타납니다.
  • 쿠키는 헤더이므로 setcookie()는 session_start(), header()와 마찬가지로 어떤 출력보다 먼저 실행되어야 합니다.

쿠키를 지우려면 같은 이름과 path, 과거의 만료 시간으로 다시 설정하세요: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']).

쿠키에 배열 저장하기

쿠키 값은 최대 약 4KB의 문자열이므로 구조화된 데이터는 JSON으로 저장하고, 돌아오는 것은 신뢰할 수 없는 것으로 취급하세요. 방문자가 바꿀 수 있습니다. 쿠키를 절대 unserialize()하지 마세요. 여러분의 클래스 객체를 만들고 매직 메서드를 실행할 수 있지만, json_decode()는 배열, 문자열, 숫자, 불리언, null, stdClass만 반환합니다.

플래그는 json_encode 페이지에서 다룹니다. 짧은 값 몇 개보다 큰 것이라면 데이터를 세션(또는 데이터베이스)에 두고 쿠키에는 키만 저장하세요.

"로그인 상태 유지" 토큰

"로그인 상태 유지" 쿠키에는 사용자 id나 비밀번호 해시를 담으면 안 됩니다. 쿠키에는 긴 무작위 토큰을, 데이터베이스에는 그 해시만 저장하고, hash_equals()로 비교하세요.

random_bytes()는 무작위이므로 실행할 때마다 값이 바뀝니다. 데이터베이스가 유출되어도 쿠키 없이는 해시가 쓸모없고, hash_equals()는 일정한 시간에 비교하므로 확인 과정이 타이밍 정보를 흘리지 않습니다.

세션과 쿠키: 무엇을 쓸까

세션($_SESSION)쿠키($_COOKIE)
저장 위치서버브라우저
방문자가 읽거나 수정 가능아니요(id만)예
크기저장소에 따라 제한쿠키당 약 4KB
유지 기간브라우저를 닫거나 세션이 만료될 때까지expires 시간까지
용도로그인한 사용자, 장바구니, CSRF 토큰, 플래시 메시지테마, 언어, 동의 선택, "로그인 상태 유지" 토큰

흔한 실수: headers already sent

session_start(), setcookie(), header()는 모두 HTTP 헤더를 보내며, 헤더는 본문보다 먼저만 보낼 수 있습니다. 포함된 파일의 <?php 앞 빈 줄이라도 출력이 먼저 나오면 다음이 발생합니다.

Warning: session_start(): Session cannot be started after headers have already been sent in /var/www/page.php on line 3
Warning: Cannot modify header information - headers already sent by (output started at /var/www/page.php:2) in /var/www/page.php on line 4

두 번째 메시지는 출력이 어디서 시작되었는지(page.php:2) 알려 줍니다. 그 줄로 가서 출력을 제거하거나 session_start()를 그 위로 옮기세요. PHP만 담긴 파일에서는 닫는 ?>를 생략하면 그 뒤의 공백이 출력이 되는 것을 막을 수 있습니다.

자주 묻는 질문

PHP에서 세션과 쿠키의 차이는 무엇인가요?

쿠키는 방문자의 브라우저에 저장되고 모든 요청과 함께 전송되므로 방문자가 읽고 바꿀 수 있습니다. 세션은 데이터를 서버에 저장하고, 브라우저는 쿠키(PHPSESSID)에 무작위 세션 id만 가집니다. 로그인한 사용자 id처럼 신뢰해야 하는 것은 $_SESSION에 넣으세요.

PHP에서 로그아웃할 때 세션을 파기하려면 어떻게 하나요?

데이터를 비우고, 세션 쿠키를 지우고, 저장된 세션을 파기하세요: $_SESSION = [];, 그다음 setcookie(session_name(), '', time() - 3600, '/'), 그다음 session_destroy();. 그 뒤 리다이렉트해서 다음 페이지가 새로 시작하게 하세요.

PHP에서 쿠키를 삭제하려면 어떻게 하나요?

같은 이름, 경로, 도메인과 과거의 만료 시간으로 다시 설정하세요: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);. $_COOKIE는 다음 요청에서야 바뀌므로, 현재 스크립트의 나머지가 그것을 읽는다면 unset($_COOKIE['theme'])도 하세요.

PHP 세션은 얼마나 유지되나요?

기본적으로 세션 쿠키는 브라우저를 닫을 때까지 유지되고(session.cookie_lifetime = 0), 서버는 활동 없이 1440초(24분)가 지나면 세션 데이터를 지울 수 있습니다(session.gc_maxlifetime). 고정된 시간 제한이 필요하면 마지막 요청 시간을 세션에 저장하고 직접 확인하세요.

session_start()가 headers already sent라고 하는 이유는 무엇인가요?

세션 id는 쿠키 헤더로 전달되고, 헤더는 어떤 출력보다 먼저 보내야 합니다. echo, HTML, 심지어 <?php 앞의 빈 줄이나 BOM도 출력을 먼저 보냅니다. 무엇이든 출력되기 전에 스크립트 맨 위에서 session_start()를 호출하세요.

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기