# 세션 기반 인증 전환 가이드 - 백엔드 (PHP/Laravel) ## 📋 개요 **목적**: JWT 토큰 기반 → 세션 기반 인증으로 전환하여 보안 강화 **주요 보안 개선 사항**: - ✅ 로그아웃 시 즉시 세션 무효화 (토큰 만료 대기 불필요) - ✅ 세션 하이재킹 실시간 감지 (IP/User-Agent 추적) - ✅ 관리자의 강제 로그아웃 기능 - ✅ 1계정 1세션 강제 (동시 로그인 제한) - ✅ 의심스러운 활동 자동 차단 --- ## 🔧 1단계: 환경 설정 ### 1.1 세션 드라이버 설정 ```bash # .env SESSION_DRIVER=redis SESSION_LIFETIME=120 # 2시간 (분 단위) SESSION_SECURE_COOKIE=true SESSION_DOMAIN=.yourdomain.com # 서브도메인 공유 시 REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 ``` ### 1.2 세션 설정 파일 ```php // config/session.php return [ 'driver' => env('SESSION_DRIVER', 'redis'), 'lifetime' => env('SESSION_LIFETIME', 120), 'expire_on_close' => false, 'encrypt' => true, // 🔒 세션 데이터 암호화 'http_only' => true, // 🔒 XSS 방지 'same_site' => 'strict', // 🔒 CSRF 방지 'secure' => env('SESSION_SECURE_COOKIE', true), // 🔒 HTTPS only // 세션 가비지 컬렉션 'lottery' => [2, 100], // 세션 쿠키 이름 'cookie' => env( 'SESSION_COOKIE', Str::slug(env('APP_NAME', 'laravel'), '_').'_session' ), ]; ``` --- ## 🔐 2단계: 인증 가드 변경 ### 2.1 Auth 설정 ```php // config/auth.php 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'session', // Sanctum → Session 변경 'provider' => 'users', ], ], ``` --- ## 🚪 3단계: 로그인 컨트롤러 수정 ### 3.1 기존 코드 (토큰 기반) ```php // ❌ 제거할 코드 public function login(Request $request) { // JWT 토큰 발급 $token = auth()->attempt($credentials); return response()->json([ 'access_token' => $token, 'refresh_token' => $refreshToken, 'token_type' => 'bearer', 'expires_in' => 7200, ]); } ``` ### 3.2 새로운 코드 (세션 기반) ```php // ✅ 새로운 로그인 로직 namespace App\Http\Controllers\Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; class LoginController extends Controller { public function login(Request $request) { // 입력 검증 $credentials = $request->validate([ 'user_id' => 'required|string', 'user_pwd' => 'required|string', ]); // 🔒 세션 기반 인증 if (Auth::attempt([ 'user_id' => $credentials['user_id'], 'password' => $credentials['user_pwd'] ], $request->filled('remember'))) { // 🔒 세션 재생성 (세션 고정 공격 방지) $request->session()->regenerate(); // 🔒 보안 정보 저장 (하이재킹 감지용) session([ 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), 'login_at' => now()->toDateTimeString(), ]); // 🔒 동시 로그인 제한 (옵션) $this->limitConcurrentSessions(Auth::user()); // 사용자 정보 반환 (토큰 없음!) return response()->json([ 'message' => 'Login successful', 'user' => [ 'id' => Auth::user()->id, 'user_id' => Auth::user()->user_id, 'name' => Auth::user()->name, 'email' => Auth::user()->email, 'phone' => Auth::user()->phone, ], 'tenant' => Auth::user()->tenant, 'menus' => Auth::user()->menus, 'roles' => Auth::user()->roles, ]); } // 인증 실패 return response()->json([ 'error' => 'Invalid credentials' ], 401); } /** * 🔒 동시 로그인 제한 (1계정 1세션) */ protected function limitConcurrentSessions($user) { // 현재 세션 ID 제외하고 모든 세션 삭제 DB::table('sessions') ->where('user_id', $user->id) ->where('id', '!=', session()->getId()) ->delete(); } } ``` --- ## 🚪 4단계: 로그아웃 컨트롤러 수정 ```php // app/Http/Controllers/Auth/LogoutController.php namespace App\Http\Controllers\Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class LogoutController extends Controller { public function logout(Request $request) { // 🔒 세션 무효화 Auth::logout(); // 🔒 세션 데이터 삭제 $request->session()->invalidate(); // 🔒 CSRF 토큰 재생성 $request->session()->regenerateToken(); return response()->json([ 'message' => 'Logged out successfully' ]); } } ``` --- ## 🛡️ 5단계: 세션 하이재킹 감지 미들웨어 ### 5.1 미들웨어 생성 ```bash php artisan make:middleware DetectSessionHijacking ``` ### 5.2 미들웨어 코드 ```php // app/Http/Middleware/DetectSessionHijacking.php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; class DetectSessionHijacking { /** * 세션 하이재킹 감지 및 차단 */ public function handle(Request $request, Closure $next) { if (Auth::check()) { $user = Auth::user(); // 🔒 IP 주소 변경 감지 if (session('ip_address') && session('ip_address') !== $request->ip()) { Log::warning('Session hijacking detected: IP changed', [ 'user_id' => $user->id, 'old_ip' => session('ip_address'), 'new_ip' => $request->ip(), ]); // 세션 파괴 및 로그아웃 Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return response()->json([ 'error' => 'Session security violation detected', 'code' => 'SESSION_HIJACKED', 'message' => 'Your session has been terminated for security reasons.' ], 401); } // 🔒 User-Agent 변경 감지 if (session('user_agent') && session('user_agent') !== $request->userAgent()) { Log::warning('Session hijacking detected: User-Agent changed', [ 'user_id' => $user->id, 'old_ua' => session('user_agent'), 'new_ua' => $request->userAgent(), ]); Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return response()->json([ 'error' => 'Session security violation detected', 'code' => 'SESSION_HIJACKED' ], 401); } } return $next($request); } } ``` ### 5.3 미들웨어 등록 ```php // app/Http/Kernel.php protected $middlewareGroups = [ 'api' => [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, \App\Http\Middleware\DetectSessionHijacking::class, // ✅ 추가 ], ]; ``` --- ## 🌐 6단계: CORS 설정 (중요!) ### 6.1 CORS 설정 파일 ```php // config/cors.php return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => [ 'http://localhost:3000', // 개발 환경 'https://yourdomain.com', // 프로덕션 'https://app.yourdomain.com', // 프로덕션 앱 ], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => true, // ✅ 세션 쿠키 전송 허용 (필수!) ]; ``` --- ## 🗑️ 7단계: 토큰 관련 코드 제거 ### 7.1 삭제할 엔드포인트 ```php // routes/api.php // ❌ 삭제: 토큰 갱신 엔드포인트 (세션은 자동 갱신) // Route::post('/refresh', [TokenController::class, 'refresh']); ``` ### 7.2 삭제할 컨트롤러 ```bash # ❌ 삭제 또는 주석 처리 # app/Http/Controllers/Auth/TokenRefreshController.php ``` --- ## ✅ 8단계: 세션 확인 엔드포인트 추가 ```php // routes/api.php Route::get('/auth/check', [AuthController::class, 'check']); ``` ```php // app/Http/Controllers/Auth/AuthController.php public function check(Request $request) { if (Auth::check()) { return response()->json([ 'authenticated' => true, 'user' => [ 'id' => Auth::user()->id, 'name' => Auth::user()->name, 'email' => Auth::user()->email, ] ]); } return response()->json([ 'authenticated' => false ]); } ``` --- ## 🧪 9단계: 테스트 ### 9.1 로그인 테스트 ```bash curl -X POST http://localhost:8000/api/v1/login \ -H "Content-Type: application/json" \ -H "X-API-KEY: your-api-key" \ -d '{"user_id": "test", "user_pwd": "password"}' \ -c cookies.txt # 쿠키 저장 # 응답: # { # "message": "Login successful", # "user": {...}, # "tenant": {...} # } # # Set-Cookie: laravel_session=abc123... ``` ### 9.2 세션 확인 테스트 ```bash curl -X GET http://localhost:8000/api/v1/auth/check \ -H "X-API-KEY: your-api-key" \ -b cookies.txt # 저장된 쿠키 사용 # 응답: # { # "authenticated": true, # "user": {...} # } ``` ### 9.3 로그아웃 테스트 ```bash curl -X POST http://localhost:8000/api/v1/logout \ -H "X-API-KEY: your-api-key" \ -b cookies.txt # 응답: # { # "message": "Logged out successfully" # } ``` ### 9.4 세션 하이재킹 감지 테스트 ```bash # 1. 로그인 (IP: A) curl -X POST http://localhost:8000/api/v1/login \ -H "X-API-KEY: your-api-key" \ -d '{"user_id": "test", "user_pwd": "password"}' \ -c cookies.txt # 2. 다른 IP에서 같은 세션 ID 사용 시도 (IP: B) # → 자동 차단되어야 함 ``` --- ## 🔒 10단계: 추가 보안 강화 (옵션) ### 10.1 Rate Limiting (무차별 대입 공격 방지) ```php // routes/api.php Route::middleware(['throttle:5,1'])->group(function () { Route::post('/login', [LoginController::class, 'login']); }); // 5번 시도 후 1분 대기 ``` ### 10.2 세션 활동 로그 ```php // app/Models/SessionLog.php 생성 Schema::create('session_logs', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('user_id'); $table->string('ip_address'); $table->text('user_agent'); $table->timestamp('login_at'); $table->timestamp('logout_at')->nullable(); $table->timestamps(); }); ``` ```php // 로그인 시 기록 SessionLog::create([ 'user_id' => Auth::id(), 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), 'login_at' => now(), ]); ``` ### 10.3 관리자 강제 로그아웃 기능 ```php // app/Http/Controllers/Admin/SessionController.php public function forceLogout(Request $request, $userId) { // 특정 사용자의 모든 세션 삭제 DB::table('sessions') ->where('user_id', $userId) ->delete(); return response()->json([ 'message' => 'User sessions terminated' ]); } ``` --- ## 📊 마이그레이션 체크리스트 ### 필수 작업 - [ ] `.env` 파일 세션 드라이버 설정 - [ ] `config/session.php` 보안 설정 적용 - [ ] `config/auth.php` 가드를 세션으로 변경 - [ ] 로그인 컨트롤러 수정 (토큰 제거, 세션 사용) - [ ] 로그아웃 컨트롤러 수정 (세션 무효화) - [ ] `config/cors.php`에서 `supports_credentials: true` 설정 - [ ] 세션 하이재킹 감지 미들웨어 추가 - [ ] `/api/v1/refresh` 엔드포인트 삭제 - [ ] `/api/v1/auth/check` 엔드포인트 추가 ### 권장 작업 - [ ] Rate Limiting 적용 - [ ] 세션 활동 로그 테이블 생성 - [ ] 관리자 강제 로그아웃 기능 구현 - [ ] 동시 로그인 제한 적용 ### 테스트 - [ ] 로그인 → 세션 생성 확인 - [ ] 로그아웃 → 세션 파괴 확인 - [ ] 세션 하이재킹 감지 테스트 - [ ] CORS 크로스 도메인 테스트 - [ ] 동시 로그인 제한 테스트 --- ## 🚨 주의사항 ### 1. 세션 저장소 (Redis) 필수 ```bash # Redis 설치 확인 redis-cli ping # 응답: PONG # Redis 접속 테스트 redis-cli > KEYS *session* ``` ### 2. CORS 설정 필수 - `supports_credentials: true` 반드시 설정 - 프론트엔드 도메인을 `allowed_origins`에 추가 - `*` (와일드카드) 사용 불가 (credentials와 충돌) ### 3. HTTPS 필수 (프로덕션) ```bash # .env SESSION_SECURE_COOKIE=true # HTTPS만 쿠키 전송 ``` ### 4. 세션 쿠키 이름 확인 ```php // config/session.php 'cookie' => 'laravel_session', // 프론트엔드에서 이 이름 사용 ``` --- ## 📞 프론트엔드 팀 공유 사항 ### API 변경 사항 **로그인 응답 변경**: ```json // ❌ 이전 (토큰 반환) { "access_token": "eyJhbG...", "refresh_token": "eyJhbG...", "token_type": "bearer", "expires_in": 7200 } // ✅ 이후 (토큰 없음, 세션 쿠키만) { "message": "Login successful", "user": {...}, "tenant": {...} } // Set-Cookie: laravel_session=abc123... ``` **필수 요구사항**: - 모든 API 호출에 `credentials: 'include'` 추가 - 세션 쿠키를 자동으로 포함하여 전송 - `/api/auth/refresh` 엔드포인트 사용 중단 --- ## 🎯 완료 후 확인사항 1. ✅ 로그인 시 세션 쿠키 생성 2. ✅ 로그아웃 시 즉시 접근 차단 3. ✅ IP 변경 시 자동 차단 4. ✅ User-Agent 변경 시 자동 차단 5. ✅ 관리자 강제 로그아웃 작동 6. ✅ Redis에 세션 데이터 저장 확인 --- ## 📚 참고 자료 - [Laravel Session 공식 문서](https://laravel.com/docs/session) - [Laravel Authentication 공식 문서](https://laravel.com/docs/authentication) - [Redis Session Driver](https://laravel.com/docs/redis) --- **작성일**: 2025-11-12 **작성자**: Claude Code **버전**: 1.0