02강: 도서 서점 라우팅 설계 & 보안 서명된 URL (Signed URLs)
실습 프로젝트: 지니샵(JinyShop) 온라인 도서 쇼핑몰
주제: HTTP 라우팅 설계, 파라미터 제약조건, 라우트 그룹, 전자책 샘플 다운로드를 위한 Signed URLs
등장인물: 🐱 지니(시니어 멘토), 👧 도로시(주니어 개발자), 🐶 토토(개발 보조견)
공식 문서 연계: 라우팅 (Routing), URL 생성 (URL Generation)
💬 1. 지니와 도로시의 티키타카 회의
👧 도로시: “지니! 우리 서점에는 책 목록 조회(/books), 책 상세 보기(/books/978896626), 장바구니 담기, 주문 결제까지 주소가 수십 개는 필요하잖아요. 나중에 주소 체계를 바꿀 때 HTML 링크를 일일이 다 고치지 않으려면 어떻게 설계해야 해요? 그리고 전자책(eBook) 미리보기 샘플 PDF 링크를 누군가 인터넷에 무단 배포하면 어쩌죠?”
🐱 지니: “도로시의 고민은 실무에서도 매일 부딪히는 문제란다! 라라벨에서는 첫째, 명명된 라우트(Named Routes)를 사용하면 URL 주소가 바뀌어도 링크가 절대 깨지지 않는단다. 둘째, 전자책 파일이나 주문 영수증처럼 민감한 URL에는 서명된 URL(Signed URLs) 마법을 걸 수 있지! 유효기간(예: 30분)과 변조 방지 디지털 서명이 포함되어, 링크를 복사해 가거나 URL 파라미터를 조작하면 즉시 403 Forbidden으로 튕겨낸단다!”
🐶 토토: “멍멍! 라우트 파라미터에 숫자가 와야 하는 책 ID 자리에 문자가 들어오지 못하게 whereNumber('id') 정규식 가드도 걸어주면 해킹 시도를 원천 차단할 수 있다멍!”
🎯 2. 실습 목표 및 서점 시나리오
- 지니샵 도서 쇼핑몰의 RESTful URL 구조를 체계적으로 설계합니다.
- 명명된 라우트(
name())와 라우트 그룹(prefix,as)을 활용해 유지보수성을 극대화합니다. - 전자책 미리보기 샘플 다운로드를 위한 30분 유효기간의 Signed URL을 생성하고 검증합니다.
🛠️ 3. 단계별 실습 절차 (Step-by-Step)
[Step 1] routes/web.php에 도서 서점 라우트 체계 구축
routes/web.php를 열고 서점의 핵심 URL 경로를 그룹 단위로 정의합니다.
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\URL;
// 1. 서점 메인 홈
Route::get('/', function () {
return view('welcome');
})->name('home');
// 2. 도서(Books) 라우트 그룹
Route::prefix('books')->name('books.')->group(function () {
// 도서 카탈로그 목록 (GET /books)
Route::get('/', function () {
return "📚 지니샵 전체 도서 목록 화면입니다.";
})->name('index');
// 베스트셀러 목록 (GET /books/bestsellers)
Route::get('/bestsellers', function () {
return "🔥 이번 주 베스트셀러 도서 목록입니다.";
})->name('bestsellers');
// 도서 상세 보기 (GET /books/{id}) - 숫자 ID 제약조건 부여
Route::get('/{id}', function (string $id) {
return "📖 도서 ID [{$id}]번 상세 정보 화면입니다.";
})->whereNumber('id')->name('show');
// ISBN 검색 (GET /books/isbn/{isbn}) - 13자리 숫자 제약
Route::get('/isbn/{isbn}', function (string $isbn) {
return "🔍 ISBN [{$isbn}] 도서 검색 결과입니다.";
})->where('isbn', '^[0-9]{13}$')->name('isbn');
});
[Step 2] 라우트 목록 확인 (route:list)
터미널에서 방금 등록한 서점 라우트들이 올바르게 등록되었는지 검사합니다:
php artisan route:list --path=books
터미널 출력:
GET|HEAD books ................................ books.index GET|HEAD books/bestsellers .................... books.bestsellers GET|HEAD books/isbn/{isbn} .................... books.isbn GET|HEAD books/{id} ........................... books.show
[Step 3] 30분 만료 서명된 URL(Signed URL) 생성 실습
전자책(eBook) 미리보기 샘플 PDF를 안전하게 다운로드할 수 있는 임시 링크 생성 엔드포인트를 구현합니다.
// routes/web.php 에 추가
// 1. 임시 다운로드 링크 발급 엔드포인트
Route::get('/books/{id}/generate-preview-url', function (int $id) {
// 현재 시간으로부터 30분 동안만 유효한 암호화 서명된 URL 생성
$downloadUrl = URL::temporarySignedRoute(
'books.download-preview',
now()->addMinutes(30),
['id' => $id, 'format' => 'pdf']
);
return response()->json([
'book_id' => $id,
'signed_download_url' => $downloadUrl,
'expires_in' => '30분',
]);
})->name('books.generate-preview');
// 2. 실제 파일 다운로드 라우트 (signed 미들웨어로 보호!)
Route::get('/books/{id}/download-preview', function (Request $request, int $id) {
// signed 미들웨어가 위조나 만료 여부를 자동 검증함
return "✅ 디지털 서명 검증 성공! [{$id}]번 도서의 샘플 eBook 다운로드를 시작합니다.";
})->name('books.download-preview')->middleware('signed');
[Step 4] 브라우저에서 Signed URL 위변조 방어 테스트
- 브라우저에서
http://127.0.0.1:8000/books/101/generate-preview-url에 접속합니다. - 발급된
signed_download_url주소를 복사하여 새 탭에 붙여넣습니다:- URL 예시:
http://127.0.0.1:8000/books/101/download-preview?expires=...&signature=... - 정상 접속되어 다운로드 메시지가 뜹니다.
- URL 예시:
- 이제 주소창에서
signature=...뒤의 글자 하나를 임의로 바꾸고 엔터를 칩니다! - 결과: 라라벨이 디지털 서명 위조를 즉시 감지하고
403 Invalid Signature에러 화면으로 차단합니다!
🔍 4. 라라벨 공식 문서 원리 심층 분석 (Deep Dive)
route() 헬퍼 함수의 우아함
- 뷰 템플릿에서
<a href="/books/{{ $book->id }}">처럼 URL 문자열을 하드코딩하면 나중에 경로를/shop/books/...로 바꿀 때 전수 수정을 해야 합니다. - 대신
<a href="{{ route('books.show', $book->id) }}">를 사용하면, 라우트 정의만 수정해도 전 사이트의 링크가 일제히 자동 갱신됩니다.
🐶 5. 토토의 트러블슈팅 & 주의사항
🐶 토토의 멍멍 가이드!
도서 상세 라우트(
books/{id})가 베스트셀러(books/bestsellers)를 먹어버려요!
라우트는 위에서부터 차례대로 매칭된다멍! 와일드카드 파라미터({id})보다 고정된 주소(bestsellers)를 반드시 위쪽에 먼저 선언해야 한다멍!모바일 손님이 Signed URL을 누를 때 앱 주소가 다르면 서명이 깨지나요?
.env의APP_URL과 실제 접속 도메인이 다르면 서명 검증이 실패할 수 있으니APP_URL을 정확히 일치시켜야 한다멍!
💡 6. 2강 자가진단 과제
- 도서 출판 연도별 아카이브 라우트(
GET /books/year/{year})를 선언하고, 4자리 연도만 들어오도록where('year', '^[0-9]{4}$')제약조건을 걸어보세요. - 유효기간이 5초인 임시 링크를 생성한 뒤, 6초 후에 접속하여 만료된 링크가 403 에러로 방어되는지 확인해 보세요.