- BOM 항목 추가/수정/삭제 시 섹션탭 즉시 반영 - 섹션 복제 시 UI 즉시 업데이트 (null vs undefined 이슈 해결) - 항목 수정 기능 추가 (useTemplateManagement) - 실시간 동기화 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
14 KiB
세션 기반 인증 전환 가이드 - 백엔드 (PHP/Laravel)
📋 개요
목적: JWT 토큰 기반 → 세션 기반 인증으로 전환하여 보안 강화
주요 보안 개선 사항:
- ✅ 로그아웃 시 즉시 세션 무효화 (토큰 만료 대기 불필요)
- ✅ 세션 하이재킹 실시간 감지 (IP/User-Agent 추적)
- ✅ 관리자의 강제 로그아웃 기능
- ✅ 1계정 1세션 강제 (동시 로그인 제한)
- ✅ 의심스러운 활동 자동 차단
🔧 1단계: 환경 설정
1.1 세션 드라이버 설정
# .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 세션 설정 파일
// 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 설정
// config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'session', // Sanctum → Session 변경
'provider' => 'users',
],
],
🚪 3단계: 로그인 컨트롤러 수정
3.1 기존 코드 (토큰 기반)
// ❌ 제거할 코드
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 새로운 코드 (세션 기반)
// ✅ 새로운 로그인 로직
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단계: 로그아웃 컨트롤러 수정
// 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 미들웨어 생성
php artisan make:middleware DetectSessionHijacking
5.2 미들웨어 코드
// 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 미들웨어 등록
// 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 설정 파일
// 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 삭제할 엔드포인트
// routes/api.php
// ❌ 삭제: 토큰 갱신 엔드포인트 (세션은 자동 갱신)
// Route::post('/refresh', [TokenController::class, 'refresh']);
7.2 삭제할 컨트롤러
# ❌ 삭제 또는 주석 처리
# app/Http/Controllers/Auth/TokenRefreshController.php
✅ 8단계: 세션 확인 엔드포인트 추가
// routes/api.php
Route::get('/auth/check', [AuthController::class, 'check']);
// 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 로그인 테스트
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 세션 확인 테스트
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 로그아웃 테스트
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 세션 하이재킹 감지 테스트
# 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 (무차별 대입 공격 방지)
// routes/api.php
Route::middleware(['throttle:5,1'])->group(function () {
Route::post('/login', [LoginController::class, 'login']);
});
// 5번 시도 후 1분 대기
10.2 세션 활동 로그
// 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();
});
// 로그인 시 기록
SessionLog::create([
'user_id' => Auth::id(),
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
'login_at' => now(),
]);
10.3 관리자 강제 로그아웃 기능
// 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) 필수
# Redis 설치 확인
redis-cli ping
# 응답: PONG
# Redis 접속 테스트
redis-cli
> KEYS *session*
2. CORS 설정 필수
supports_credentials: true반드시 설정- 프론트엔드 도메인을
allowed_origins에 추가 *(와일드카드) 사용 불가 (credentials와 충돌)
3. HTTPS 필수 (프로덕션)
# .env
SESSION_SECURE_COOKIE=true # HTTPS만 쿠키 전송
4. 세션 쿠키 이름 확인
// config/session.php
'cookie' => 'laravel_session', // 프론트엔드에서 이 이름 사용
📞 프론트엔드 팀 공유 사항
API 변경 사항
로그인 응답 변경:
// ❌ 이전 (토큰 반환)
{
"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엔드포인트 사용 중단
🎯 완료 후 확인사항
- ✅ 로그인 시 세션 쿠키 생성
- ✅ 로그아웃 시 즉시 접근 차단
- ✅ IP 변경 시 자동 차단
- ✅ User-Agent 변경 시 자동 차단
- ✅ 관리자 강제 로그아웃 작동
- ✅ Redis에 세션 데이터 저장 확인
📚 참고 자료
작성일: 2025-11-12 작성자: Claude Code 버전: 1.0