HTTP Session
- Introduction
- Interacting With the Session
- Session Cache
- Session Blocking
- Adding Custom Session Drivers
Introduction
HTTP 기반 애플리케이션은 상태를 저장하지 않으므로, 세션은 여러 요청에 걸쳐 사용자 정보를 저장할 수 있는 방법을 제공합니다. 이러한 사용자 정보는 일반적으로 이후의 요청에서 접근할 수 있는 영속성 저장소 또는 백엔드에 저장됩니다.
Laravel은 다양한 세션 백엔드를 기본적으로 제공하며, 이를 표현력 있고 통합된 API를 통해 사용할 수 있습니다. Memcached, Redis, 데이터베이스 등과 같은 인기 있는 백엔드를 지원합니다.
Configuration
애플리케이션의 세션 구성 파일은 config/session.php에 위치합니다. 이 파일에서 제공되는 옵션들을 반드시 검토하시기 바랍니다. Laravel은 기본적으로 database 세션 드라이버를 사용하도록 설정되어 있습니다.
세션의 driver 구성 옵션은 각 요청에서 세션 데이터가 저장될 위치를 정의합니다. Laravel은 다양한 드라이버를 포함합니다:
file- 세션이storage/framework/sessions디렉토리에 저장됩니다.cookie- 세션이 보안이 유지되고 암호화된 쿠키에 저장됩니다.database- 세션이 관계형 데이터베이스에 저장됩니다.memcached/redis- 세션이 이러한 빠른 캐시 기반 저장소 중 하나에 저장됩니다.dynamodb- 세션이 AWS DynamoDB에 저장됩니다.array- 세션이 PHP 배열에 저장되며, 영속적으로 저장되지 않습니다.
array드라이버는 주로 testing할 때 사용되며, 세션에 저장된 데이터가 영속적으로 남지 않습니다.
Driver Prerequisites
Database
database 세션 드라이버를 사용할 때는 세션 데이터를 저장할 데이터베이스 테이블이 필요합니다. 보통 Laravel 기본 database migration인 0001_01_01_000000_create_users_table.php에 포함되어 있지만, 만약 sessions 테이블이 없다면, 다음과 같이 make:session-table Artisan 명령어를 사용해 마이그레이션을 생성할 수 있습니다:
php artisan make:session-table
php artisan migrate
Redis
Laravel에서 Redis 세션을 사용하기 전에, PECL을 통해 PhpRedis PHP 확장 모듈을 설치하거나 Composer를 통해 predis/predis 패키지를 설치해야 합니다. Redis 설정에 대한 자세한 내용은 Laravel의 Redis documentation를 참고하십시오.
세션 스토리지로 사용할 Redis 연결을 지정하려면
SESSION_CONNECTION환경 변수 또는session.php구성 파일의connection옵션을 사용할 수 있습니다.
Interacting With the Session
Retrieving Data
Laravel에서 세션 데이터를 다루는 대표적인 방법은 두 가지가 있습니다: 전역 session 헬퍼를 사용하는 방법과 Request 인스턴스를 사용하는 방법입니다. 먼저 Request 인스턴스를 통해 세션에 접근하는 방법을 살펴보겠습니다. 이 인스턴스는 라우트 클로저나 컨트롤러 메서드에 타입힌트를 지정하여 주입받을 수 있습니다. 컨트롤러의 의존성은 Laravel service container를 통해 자동으로 주입됩니다:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
public function show(Request $request, string $id): View
{
$value = $request->session()->get('key');
// ...
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
세션에서 항목을 조회할 때는 get 메서드의 두 번째 인수로 기본값을 지정할 수 있습니다. 지정한 키가 세션에 존재하지 않을 경우, 해당 기본값이 반환됩니다. 또한, get 메서드의 기본값에 클로저를 전달하면, 요청한 키가 존재하지 않을 때 클로저가 실행되어 결과가 반환됩니다:
$value = $request->session()->get('key', 'default');
$value = $request->session()->get('key', function () {
return 'default';
});
The Global Session Helper
세션 데이터를 조회하거나 저장할 때 전역 session PHP 함수를 사용할 수 있습니다. session 헬퍼를 문자열 하나로 호출하면 해당 세션 키의 값을 반환합니다. 배열 형태로 키/값 쌍을 전달하면 해당 값들이 세션에 저장됩니다:
Route::get('/home', function () {
// Retrieve a piece of data from the session...
$value = session('key');
// Specifying a default value...
$value = session('key', 'default');
// Store a piece of data in the session...
session(['key' => 'value']);
});
HTTP 요청 인스턴스를 통해 세션을 사용하는 것과 전역 session 헬퍼를 사용하는 것 사이에는 거의 실질적인 차이가 없습니다. 모든 테스트 케이스에서 사용할 수 있는
assertSessionHas메서드로 두 방법 모두 테스트할 수 있습니다(testable).
Retrieving All Session Data
세션에 저장된 모든 데이터를 조회하려면 all 메서드를 사용하면 됩니다:
$data = $request->session()->all();
Retrieving a Portion of the Session Data
세션 데이터 일부만 조회하려면 only 및 except 메서드를 사용할 수 있습니다:
$data = $request->session()->only(['username', 'email']);
$data = $request->session()->except(['username', 'email']);
Determining if an Item Exists in the Session
세션에 항목이 존재하는지 확인하려면 has 메서드를 사용할 수 있습니다. has 메서드는 해당 항목이 존재하고 값이 null이 아니면 true를 반환합니다:
if ($request->session()->has('users')) {
// ...
}
값이 null이어도 세션에 항목이 존재하는지 확인하려면 exists 메서드를 사용할 수 있습니다:
if ($request->session()->exists('users')) {
// ...
}
세션에 항목이 존재하지 않는지 확인하려면 missing 메서드를 사용합니다. missing 메서드는 항목이 없으면 true를 반환합니다:
if ($request->session()->missing('users')) {
// ...
}
Storing Data
세션에 데이터를 저장할 때는 보통 요청 인스턴스의 put 메서드나 전역 session 헬퍼를 사용합니다:
// Via a request instance...
$request->session()->put('key', 'value');
// Via the global "session" helper...
session(['key' => 'value']);
Pushing to Array Session Values
세션 값이 배열일 경우, push 메서드를 사용해 새로운 값을 배열에 추가할 수 있습니다. 예를 들어, user.teams 키가 팀 이름의 배열을 포함하고 있다면 다음과 같이 새로운 값 추가가 가능합니다:
$request->session()->push('user.teams', 'developers');
Retrieving and Deleting an Item
pull 메서드는 세션에서 항목을 조회함과 동시에 삭제합니다:
$value = $request->session()->pull('key', 'default');
Incrementing and Decrementing Session Values
세션 데이터에 정수형 값을 저장하고 있고, 이를 증가 또는 감소시키고 싶다면 increment, decrement 메서드를 사용할 수 있습니다:
$request->session()->increment('count');
$request->session()->increment('count', $incrementBy = 2);
$request->session()->decrement('count');
$request->session()->decrement('count', $decrementBy = 2);
Flash Data
가끔 특정 데이터를 다음 요청까지 세션에 임시로 저장하고 싶을 때가 있습니다. 이럴 때 flash 메서드를 사용합니다. 이 메서드로 세션에 저장된 데이터는 즉시 사용할 수 있으며, 다음 HTTP 요청에도 접근할 수 있습니다. 이후의 요청이 끝나면 플래시 데이터는 삭제됩니다. 플래시 데이터는 주로 단기간의 상태 메시지 등에 유용합니다:
$request->session()->flash('status', 'Task was successful!');
플래시 데이터를 여러 요청에 걸쳐 유지하고 싶다면 reflash 메서드를 사용해 모든 플래시 데이터를 추가 요청 한 번 동안 유지할 수 있습니다. 특정 플래시 데이터만 유지하려면 keep 메서드를 사용합니다:
$request->session()->reflash();
$request->session()->keep(['username', 'email']);
현재 요청에만 플래시 데이터를 유지하고 싶다면 now 메서드를 사용할 수 있습니다:
$request->session()->now('status', 'Task was successful!');
Deleting Data
forget 메서드는 세션에서 특정 데이터를 삭제합니다. 만약 세션의 모든 데이터를 삭제하고 싶다면 flush 메서드를 사용할 수 있습니다:
// Forget a single key...
$request->session()->forget('name');
// Forget multiple keys...
$request->session()->forget(['name', 'status']);
$request->session()->flush();
Regenerating the Session ID
세션 ID 재생성은 주로 악의적인 사용자의 session fixation 공격으로부터 애플리케이션을 보호하는 데 사용됩니다.
Laravel은 application starter kits나 Laravel Fortify를 사용할 경우 인증 시 자동으로 세션 ID를 재생성합니다. 다만, 수동으로 세션 ID를 재생성해야 한다면 regenerate 메서드를 사용할 수 있습니다:
$request->session()->regenerate();
세션 ID를 재생성하면서 동시에 모든 세션 데이터를 삭제하려면 invalidate 메서드를 사용할 수 있습니다:
$request->session()->invalidate();
Session Cache
Laravel의 세션 캐시는 개별 사용자 세션에 범위가 한정된 데이터를 편리하게 캐싱할 수 있는 기능을 제공합니다. 글로벌 애플리케이션 캐시와 달리, 세션 캐시 데이터는 세션마다 자동으로 분리되어 관리되며, 세션이 만료되거나 삭제될 때 함께 정리됩니다. 세션 캐시는 현재 세션에 한정해서 Laravel cache methods인 get, put, remember, forget 등 익숙한 기능들을 사용할 수 있습니다.
세션 캐시는 동일 세션 내에서 여러 요청에 걸쳐 임시로 사용자별 데이터를 저장하고 싶을 때 적합합니다. 예를 들어, 폼 데이터, 임시 계산 값, API 응답 등 특정 사용자의 세션에만 일시적으로 남아 있어야 할 휘발성 데이터를 저장할 수 있습니다.
세션 캐시는 session의 cache 메서드를 통해 접근할 수 있습니다:
$discount = $request->session()->cache()->get('discount');
$request->session()->cache()->put(
'discount', 10, now()->plus(minutes: 5)
);
Laravel 캐시 메서드에 대한 자세한 정보는 cache documentation를 참고하십시오.
Session Blocking
세션 블로킹을 사용하려면, 애플리케이션이 atomic locks을 지원하는 캐시 드라이버를 사용해야 합니다. 현재 지원되는 드라이버는
memcached,dynamodb,redis,mongodb(공식mongodb/laravel-mongodb패키지 포함),database,file, array 드라이버입니다. 또한cookie세션 드라이버는 사용할 수 없습니다.
Laravel은 기본적으로 동일한 세션을 사용하는 여러 요청이 동시에 실행될 수 있도록 허용합니다. 예를 들어 JavaScript HTTP 라이브러리를 사용하여 동시에 두 번의 HTTP 요청을 보내면, 두 요청이 동시에 처리됩니다. 대부분의 애플리케이션에서는 큰 문제가 없으나, 서로 다른 두 엔드포인트가 모두 세션에 데이터를 쓰고 여기에 동시 요청을 보내는 일부 애플리케이션에서는 세션 데이터가 유실될 수 있습니다.
이 문제를 예방하기 위해, Laravel은 특정 세션에 대한 동시 요청을 제한하는 기능을 제공합니다. 사용하려면 라우트 정의에 block 메서드를 체이닝하면 됩니다. 아래 예시에서 /profile 엔드포인트로 들어오는 요청은 세션 락을 획득합니다. 락이 유지되는 동안 동일 세션 ID를 공유하는 또 다른 /profile 또는 /order 요청은 첫 번째 요청이 끝날 때까지 대기합니다:
Route::post('/profile', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10);
Route::post('/order', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10);
block 메서드는 2개의 선택적 인수를 받습니다. block 메서드가 받는 첫 번째 인수는 세션 락이 최대 몇 초간 유지될지 지정합니다. 요청이 더 빨리 끝나면 락도 더 빨리 해제됩니다.
두 번째 인수는 요청이 세션 락 획득을 시도하면서 얼마나 오래 대기할지 지정합니다. block 메서드가 받은 시간 내에 락을 얻지 못하면 Illuminate\Contracts\Cache\LockTimeoutException 예외가 발생합니다.
두 인수를 모두 생략하면 기본값은 각각 최대 10초 동안 락을 잡고, 10초 동안 락을 얻으려고 시도하게 됩니다:
Route::post('/profile', function () {
// ...
})->block();
Adding Custom Session Drivers
Implementing the Driver
기존 세션 드라이버가 애플리케이션의 요구 사항에 맞지 않는 경우, 직접 세션 핸들러를 구현할 수 있습니다. 사용자 정의 세션 드라이버는 PHP의 내장 SessionHandlerInterface를 구현해야 합니다. 이 인터페이스는 몇 가지 간단한 메서드만을 포함합니다. 다음은 MongoDB를 예시로 삼은 기본적인 구조입니다:
<?php
namespace App\Extensions;
class MongoSessionHandler implements \SessionHandlerInterface
{
public function open($savePath, $sessionName) {}
public function close() {}
public function read($sessionId) {}
public function write($sessionId, $data) {}
public function destroy($sessionId) {}
public function gc($lifetime) {}
}
Laravel은 확장 코드를 저장할 기본 디렉터리를 제공하지 않으므로, 원하는 위치에 자유롭게 파일을 둘 수 있습니다. 여기서는 Extensions 디렉터리에 MongoSessionHandler를 생성했습니다.
각 메서드의 목적이 명확하지 않을 수 있으니, 아래에 간단히 설명합니다:
open메서드는 일반적으로 파일 기반 세션 저장 시스템에서 사용됩니다. Laravel은file세션 드라이버를 제공하므로, 이 메서드에 코드를 작성할 일은 거의 없습니다. 메서드를 비워두어도 됩니다.close메서드도open메서드와 마찬가지로 보통은 신경 쓰지 않아도 됩니다. 대부분의 드라이버에서는 필요하지 않습니다.read메서드는 주어진$sessionId와 연결된 세션 데이터를 문자열로 반환해야 합니다. 드라이버에서 세션 데이터를 가져오거나 저장할 때 직렬화하거나 다른 방식으로 인코딩할 필요는 없습니다. Laravel이 직렬화를 처리하기 때문입니다.write메서드는$sessionId와 연결된 주어진$data문자열을 MongoDB나 원하는 다른 영속성 저장소에 기록해야 합니다. 이때도 직접 직렬화해서는 안 됩니다. Laravel이 이미 처리했기 때문입니다.destroy메서드는$sessionId와 연결된 데이터를 영속성 저장소에서 삭제해야 합니다.gc메서드는 초 단위로 지정된 주어진$lifetime보다 오래된 세션 데이터를 모두 삭제해야 합니다. Memcached나 Redis처럼 데이터가 자동으로 만료되는 시스템에서는 이 메서드를 비워두어도 됩니다.
Registering the Driver
드라이버 구현이 완료되었다면 Laravel에 이를 등록해야 합니다. 추가적인 세션 드라이버는 Session facade가 제공하는 extend 메서드로 등록할 수 있습니다. service provider의 boot 메서드에서 extend를 호출해야 하며, 기존 App\Providers\AppServiceProvider에서 하거나 별도의 프로바이더를 새로 만들어도 됩니다:
<?php
namespace App\Providers;
use App\Extensions\MongoSessionHandler;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
class SessionServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Session::extend('mongo', function (Application $app) {
// Return an implementation of SessionHandlerInterface...
return new MongoSessionHandler;
});
}
}
세션 드라이버 등록이 완료되면, SESSION_DRIVER 환경 변수나 애플리케이션의 config/session.php 구성 파일을 통해 세션 드라이버로 mongo를 지정할 수 있습니다.