제5.4장: CSRF 토큰 작업
학습 시간: 30분
1. CSRF 공격이란 무엇인가? 당신의 함선 '납치'
당신이 우주 함대(space-api.test
) 제어판에 로그인되어 있다고 상상해 보세요. 옆 탭에서 고양이 사진이 있는 무해한 웹사이트(evil-cats.com
)를 엽니다. 이 웹사이트에는 당신의 웹사이트 주소인 POST /api/planets/1/delete
로 자동으로 요청을 보내는 숨겨진 양식이 있습니다.
당신이 이미 space-api.test
에 인증되어 있으므로, 당신의 브라우저는 이 요청에 당신의 모든 쿠키를 친절하게 첨부할 것입니다. Laravel 서버는 유효한 세션을 확인하고 당신이 직접 행성을 폐기하기로 결정했다고 생각할 것입니다. 당신도 모르는 사이에 행성이 삭제될 것입니다.
이것이 바로 CSRF (교차 사이트 요청 위조)입니다. 이는 공격자가 인증된 사용자의 브라우저로 하여금 신뢰할 수 있는 웹사이트에서 원치 않는 작업을 수행하도록 강제하는 공격입니다.
💡 우주 비유:
당신은 함선의 선장이며, 키 카드(세션/쿠키)를 가지고 있습니다. 공격자는 당신의 카드를 훔칠 수 없습니다. 하지만 당신이 한눈을 판 사이에 자원 폐기 터미널에 카드를 대도록 속일 수 있습니다. CSRF 토큰은 카드와 함께 입력해야 하는 PIN 코드와 같습니다. 공격자는 PIN 코드를 모르므로, 그의 공격은 실패합니다.
2. Laravel은 CSRF로부터 어떻게 보호하는가?
Laravel은 기본적으로 모든 "안전하지 않은" 웹 요청(POST, PUT, PATCH, DELETE)을 CSRF 토큰을 사용하여 보호합니다.
- 페이지를 생성할 때 Laravel은 사용자 세션에 대해 고유하고 무작위적인 토큰을 생성합니다.
- 이 토큰은 HTML 양식에 삽입됩니다.
- 양식을 제출할 때 토큰은 요청과 함께 전송됩니다.
- 서버에서는
VerifyCsrfToken
미들웨어가 요청의 토큰과 세션에 저장된 토큰을 비교합니다. - 토큰이 일치하지 않으면 Laravel은 419 오류(세션 만료/페이지 만료)와 함께 요청을 중단합니다.
중요: routes/api.php
의 API 경로는 쿠키 기반 세션이 아닌 다른 인증 메커니즘(예: Sanctum 토큰)을 가정하므로 CSRF 보호가 없습니다. 현재 우리의 문제는 routes/web.php
에서 생성하는 웹 경로 및 페이지에 해당합니다.
3. HTML 양식에서 CSRF 토큰 사용
이것은 가장 간단한 시나리오입니다. Laravel은 이를 위해 특별한 Blade 지시어를 제공합니다.
예시: 행성 생성 양식
간단한 양식을 resources/views/planets/create.blade.php
파일에 생성해 봅시다:
<h2>새 행성 시작 양식</h2>
<form action="/planets" method="POST">
@csrf {{-- 바로 이것이 마법! --}}
<label for="name">이름:</label>
<input type="text" id="name" name="name" required>
<label for="solar_system">태양계:</label>
<input type="text" id="solar_system" name="solar_system" required>
{{-- ... 기타 필드 ... --}}
<button type="submit">시작</button>
</form>
@csrf
지시어는 양식에 숨겨진 필드를 자동으로 생성합니다:
이것으로 표준 HTML 양식을 보호하기에 충분합니다.
4. AJAX/Fetch 요청에서 CSRF 토큰 사용
지난 장에서는 JavaScript를 사용하여 DELETE
요청을 보냈습니다. 이제 Laravel은 419 오류로 이를 차단할 것입니다. Fetch 요청의 헤더에 CSRF 토큰을 추가해야 합니다.
단계 1: JavaScript에서 토큰에 접근 가능하게 만들기
마스터 레이아웃 resources/views/app.blade.php
의 <head>
에 토큰이 포함된 메타 태그를 추가하세요. 이는 Laravel의 표준 관행입니다.
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{-- 메타 태그에 CSRF 토큰 추가 --}}
<meta name="csrf-token" content="{{ csrf_token() }}">
{{-- ... --}}
</head>
csrf_token()
함수는 현재 토큰을 반환합니다.
단계 2: 토큰 전송을 위한 JavaScript 수정
이제 public/js/planets.js
에서 이 토큰을 읽고 모든 "안전하지 않은" 요청의 헤더에 추가할 수 있습니다.
// ... public/js/planets.js 파일에서 ...
document.addEventListener('DOMContentLoaded', () => {
// 메타 태그에서 토큰 가져오기
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(button => {
button.addEventListener('click', async (event) => {
// ... 확인 로직 ...
try {
const response = await fetch(apiUrl, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': csrfToken // <-- 헤더에 토큰 추가!
}
});
// ... 응답 처리의 나머지 로직 ...
} catch (error) {
// ...
}
});
});
});
- 헤더 이름
X-CSRF-TOKEN
은 Laravel이 기본적으로 확인하는 표준입니다.
이제 우리의 AJAX 요청도 보호됩니다. 행성을 다시 삭제해 보세요. 이번에는 요청이 성공적으로 처리될 것입니다.
복습 퀴즈
🚀 장 요약:
우주선에 "아군/적군 식별 시스템"을 설치하여 CSRF 공격으로부터 보호했습니다. 다음을 학습했습니다:
- CSRF 공격의 본질과 위험성을 이해합니다.
@csrf
디렉티브를 사용하여 표준 HTML 폼을 보호합니다.- 메타 태그를 통해 CSRF 토큰을 JavaScript로 전달합니다.
- 성공적인 실행을 위해 AJAX/Fetch 요청 헤더에 토큰을 포함합니다.
이제 웹 인터페이스는 상호작용적일 뿐만 아니라 안전합니다. 다음 장에서는 웹 페이지의 라우팅을 올바르게 구성하는 방법을 살펴보며 웹 인터페이스 생성을 완료할 것입니다.