Laravel Middleware 완벽 가이드: 인증, 속도 제한, 커스텀 미들웨어

Laravel 미들웨어를 실용적인 예제와 함께 설명합니다. 인증 가드, throttle을 사용한 속도 제한, 커스텀 미들웨어 생성, Laravel 13의 PHP 속성, 프로덕션 애플리케이션을 위한 고급 패턴을 다룹니다.

Laravel 미들웨어 아키텍처 - 요청 파이프라인, 인증, 속도 제한 구조 다이어그램

Laravel 미들웨어는 들어오는 HTTP 요청과 애플리케이션 로직 사이에 위치하는 필터 계층으로 작동합니다. 모든 요청은 컨트롤러에 도달하기 전에 미들웨어 클래스의 파이프라인을 통과하고, 모든 응답은 동일한 파이프라인을 역방향으로 통과합니다. 이 메커니즘의 이해는 안전하고 고성능의 Laravel 애플리케이션을 구축하는 데 필수적입니다.

미들웨어 개요

미들웨어는 HTTP 요청이 라우트에 도달하기 전에 가로챕니다. Laravel은 bootstrap/app.php에서 Fluent API를 사용하여 모든 미들웨어를 등록합니다. 내장 미들웨어는 인증, CSRF 보호, 세션 관리, 속도 제한을 기본으로 처리합니다.

Laravel 미들웨어 파이프라인의 작동 원리

Laravel HTTP 커널은 모든 요청을 미들웨어 스택을 통해 처리합니다. 각 미들웨어는 요청을 받고, 로직을 수행하며, $next($request)를 통해 다음 계층으로 요청을 전달하거나 응답을 직접 반환하여 파이프라인을 단락시킵니다.

이 아키텍처는 Chain of Responsibility 패턴을 따릅니다. 미들웨어는 요청이 컨트롤러에 도달하기 전(예: 인증 검사), 응답이 생성된 후(예: 헤더 추가), 또는 양쪽 모두에서 동작할 수 있습니다.

app/Http/Middleware/LogRequestTime.phpphp
namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesLog;
use SymfonyComponentHttpFoundationResponse;

class LogRequestTime
{
    public function handle(Request $request, Closure $next): Response
    {
        $start = microtime(true);          // Capture start time

        $response = $next($request);       // Pass to next middleware

        $duration = microtime(true) - $start;
        Log::info('Request completed', [
            'url'      => $request->url(),
            'method'   => $request->method(),
            'duration' => round($duration * 1000, 2) . 'ms',
        ]);

        return $response;                  // Return response up the stack
    }
}

이 미들웨어는 요청을 감쌉니다. 처리 전에 시작 시간을 기록하고 응답이 돌아온 후에 소요 시간을 로깅합니다. 이 before/after 패턴이 미들웨어 작동의 핵심입니다.

인증 미들웨어: 라우트 보호

Laravel은 IlluminateAuthMiddlewareAuthenticate에 매핑된 auth 미들웨어 별칭을 제공합니다. 라우트에 적용하면 인증된 사용자만 접근할 수 있도록 보장합니다. 인증되지 않은 사용자는 401 응답(API)을 받거나 로그인 페이지로 리다이렉트됩니다(웹).

routes/web.phpphp
use AppHttpControllersDashboardController;
use AppHttpControllersProfileController;

// Single route protection
Route::get('/dashboard', [DashboardController::class, 'index'])
    ->middleware('auth');

// Group protection for multiple routes
Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'show']);
    Route::put('/profile', [ProfileController::class, 'update']);
    Route::delete('/profile', [ProfileController::class, 'destroy']);
});

다중 가드 인증

여러 사용자 유형(관리자 패널, 고객 영역, API)이 있는 애플리케이션은 가드 기반 인증의 혜택을 받습니다. auth 미들웨어는 사용할 인증 드라이버를 지정하는 가드 매개변수를 받습니다.

routes/api.phpphp
// API routes use the 'sanctum' guard
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn (Request $request) => $request->user());
    Route::apiResource('/orders', OrderController::class);
});

// routes/web.php
// Admin routes use a custom 'admin' guard
Route::middleware('auth:admin')->prefix('admin')->group(function () {
    Route::get('/dashboard', [AdminController::class, 'index']);
    Route::get('/users', [AdminController::class, 'users']);
});

콜론 뒤의 가드 매개변수는 Laravel에게 어떤 인증 설정에 대해 검증할지 알려줍니다. 이를 통해 애플리케이션의 다른 부분에서 인증 로직을 깔끔하게 분리할 수 있습니다.

guest 미들웨어

guest 미들웨어는 auth의 반대로, 인증되지 않은 사용자만 통과시킵니다. 로그인 및 회원가입 라우트에 적용하면 이미 인증된 사용자가 해당 페이지에 접근하는 것을 방지합니다.

Throttle 미들웨어를 사용한 속도 제한

Laravel 미들웨어 속도 제한은 내장 throttle 미들웨어를 사용하여 라우트를 남용으로부터 보호합니다. 가장 간단한 형태는 두 개의 매개변수를 받습니다: 최대 요청 수와 분 단위의 시간 창입니다.

routes/api.phpphp
// Allow 60 requests per minute per user
Route::middleware('throttle:60,1')->group(function () {
    Route::get('/posts', [PostController::class, 'index']);
    Route::get('/posts/{post}', [PostController::class, 'show']);
});

// Stricter limit for write operations
Route::middleware(['auth:sanctum', 'throttle:10,1'])->group(function () {
    Route::post('/posts', [PostController::class, 'store']);
    Route::put('/posts/{post}', [PostController::class, 'update']);
});

고급 제어를 위한 명명된 Rate Limiter

AppServiceProvider에서 명명된 Rate Limiter를 정의하면 사용자 컨텍스트에 기반한 세밀한 제어가 가능합니다. 이 접근 방식은 인라인 throttle 매개변수보다 유연합니다. 추가 옵션은 공식 속도 제한 문서를 참조하십시오.

app/Providers/AppServiceProvider.phpphp
use IlluminateCacheRateLimitingLimit;
use IlluminateSupportFacadesRateLimiter;
use IlluminateHttpRequest;

public function boot(): void
{
    // API rate limiter with tiered access
    RateLimiter::for('api', function (Request $request) {
        $user = $request->user();

        if ($user?->hasSubscription('enterprise')) {
            return Limit::perMinute(500)->by($user->id);   // Enterprise: 500/min
        }

        if ($user) {
            return Limit::perMinute(100)->by($user->id);   // Authenticated: 100/min
        }

        return Limit::perMinute(20)->by($request->ip());   // Anonymous: 20/min
    });

    // Login limiter to prevent brute force
    RateLimiter::for('login', function (Request $request) {
        return Limit::perMinute(5)
            ->by($request->ip())                            // Key by IP address
            ->response(function () {                        // Custom exceeded response
                return response()->json([
                    'message' => 'Too many login attempts. Try again in a minute.',
                ], 429);
            });
    });
}

명명된 Limiter를 라우트에 적용하려면 throttle:name 구문을 사용합니다:

routes/api.phpphp
Route::middleware('throttle:api')->group(function () {
    Route::apiResource('/posts', PostController::class);
});

// routes/web.php
Route::middleware('throttle:login')
    ->post('/login', [AuthController::class, 'login']);

위의 계층형 Rate Limiter는 프로덕션 패턴을 보여줍니다. 엔터프라이즈 사용자는 더 높은 제한을 받고, 인증된 사용자는 중간 정도의 제한을 받으며, 익명 요청은 공격적으로 제한됩니다. by() 메서드는 Rate Limit 키를 결정하며, 인증된 사용자에게는 사용자 ID를, 폴백으로는 IP 주소를 사용합니다.

Laravel 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

처음부터 커스텀 미들웨어 구축

내장 미들웨어가 처리하지 않는 시나리오에는 커스텀 미들웨어를 생성합니다. make:middleware Artisan 명령은 올바른 구조를 가진 새 클래스를 스캐폴딩합니다.

bash
php artisan make:middleware EnsureUserHasRole

역할 기반 접근 제어 미들웨어

일반적인 커스텀 미들웨어 패턴은 라우트 수준에서 역할 기반 인가를 적용하며, 역할 이름을 매개변수로 받습니다.

app/Http/Middleware/EnsureUserHasRole.phpphp
namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;

class EnsureUserHasRole
{
    public function handle(Request $request, Closure $next, string ...$roles): Response
    {
        $user = $request->user();

        if (! $user || ! $user->hasAnyRole($roles)) {
            abort(403, 'Insufficient permissions.');
        }

        return $next($request);
    }
}

가변 인자 ...$roles 매개변수는 쉼표로 구분된 여러 역할을 전달할 수 있게 합니다. 등록 및 사용 방법은 다음과 같습니다:

bootstrap/app.phpphp
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'role' => AppHttpMiddlewareEnsureUserHasRole::class,
    ]);
})

// routes/web.php
Route::middleware('role:admin')->group(function () {
    Route::get('/admin', [AdminController::class, 'index']);
});

// Multiple roles: admin OR editor can access
Route::middleware('role:admin,editor')->group(function () {
    Route::resource('/articles', ArticleController::class);
});

요청 변환 미들웨어

미들웨어는 컨트롤러에 도달하기 전에 요청을 수정할 수 있습니다. 콘텐츠 타입 헤더를 강제하고 문자열 입력을 트리밍하는 JSON API 미들웨어 예제입니다:

app/Http/Middleware/ApiRequestSanitizer.phpphp
namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationResponse;

class ApiRequestSanitizer
{
    public function handle(Request $request, Closure $next): Response
    {
        // Reject non-JSON requests on API routes
        if (! $request->expectsJson() && $request->isMethod('POST')) {
            return response()->json(
                ['error' => 'Content-Type must be application/json'],
                415
            );
        }

        // Trim all string inputs
        $input = $request->all();
        array_walk_recursive($input, function (&$value) {
            if (is_string($value)) {
                $value = trim($value);
            }
        });
        $request->merge($input);

        return $next($request);
    }
}

이 미들웨어는 두 가지 관심사를 처리합니다. POST 요청의 콘텐츠 타입을 검증하고 모든 문자열 입력의 공백을 제거하여 정제합니다.

bootstrap/app.php에서 미들웨어 등록

Laravel은 모든 미들웨어 등록을 bootstrap/app.php에 중앙 집중화합니다. 이는 Laravel 11 이전에 존재했던 app/Http/Kernel.php 방식을 대체합니다. 동일한 Fluent API가 Laravel 12와 13에서도 작동합니다.

bootstrap/app.phpphp
use IlluminateFoundationApplication;
use IlluminateFoundationConfigurationMiddleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware) {
        // Global middleware (runs on every request)
        $middleware->append(
            AppHttpMiddlewareLogRequestTime::class
        );

        // Add to the 'web' middleware group
        $middleware->web(append: [
            AppHttpMiddlewareTrackPageViews::class,
        ]);

        // Add to the 'api' middleware group
        $middleware->api(prepend: [
            AppHttpMiddlewareApiRequestSanitizer::class,
        ]);

        // Register aliases for route-level use
        $middleware->alias([
            'role'       => AppHttpMiddlewareEnsureUserHasRole::class,
            'subscribed' => AppHttpMiddlewareEnsureUserIsSubscribed::class,
        ]);

        // Control execution order
        $middleware->priority([
            IlluminateSessionMiddlewareStartSession::class,
            IlluminateAuthMiddlewareAuthenticate::class,
            AppHttpMiddlewareEnsureUserHasRole::class,
        ]);
    })
    ->create();

priority 배열은 동일한 라우트에 여러 미들웨어가 할당된 경우 중요합니다. Laravel은 이 목록에 따라 정렬하여 세션이 인증 전에 시작되고, 인증이 역할 검사 전에 완료되도록 보장합니다.

미들웨어 실행 순서

미들웨어는 등록된 순서대로 실행됩니다. 라우트 수준 미들웨어의 경우 priority 배열이 기본 순서를 재정의합니다. 인증되지 않은 요청에서 역할을 검사하는 것을 피하기 위해 항상 인증을 인가 미들웨어 전에 배치하십시오.

Laravel 13의 PHP 속성을 사용한 미들웨어

Laravel 13에서는 컨트롤러 클래스 및 메서드에 직접 미들웨어를 선언하기 위한 #[Middleware] PHP 속성이 도입되었습니다. 이 접근 방식은 미들웨어 구성을 보호하는 코드와 같은 위치에 배치하여 인가 규칙을 읽고 유지 관리하기 쉽게 만듭니다.

app/Http/Controllers/CommentController.phpphp
namespace AppHttpControllers;

use AppModelsComment;
use AppModelsPost;
use IlluminateRoutingAttributesControllersAuthorize;
use IlluminateRoutingAttributesControllersMiddleware;

#[Middleware('auth')]
class CommentController
{
    #[Middleware('subscribed')]
    #[Authorize('create', [Comment::class, 'post'])]
    public function store(Post $post)
    {
        // Only authenticated, subscribed users who can create comments reach here
    }

    public function index(Post $post)
    {
        // Still requires auth (from class-level attribute)
        return $post->comments;
    }
}

클래스 수준의 #[Middleware('auth')]는 모든 메서드에 적용됩니다. 메서드 수준 속성은 그 위에 쌓입니다. store()는 auth와 subscribed 모두를 필요로 합니다. #[Authorize] 속성은 Laravel의 정책 시스템과 통합되어 메서드 실행 전에 권한을 확인합니다.

이 속성 기반 접근 방식은 선택 사항입니다. 라우트 파일 미들웨어와 bootstrap/app.php 등록은 여전히 완전히 지원됩니다. 명시적인 라우트 정의를 선호하는 팀은 Fluent API를 계속 사용할 수 있고, 자체 문서화되는 컨트롤러를 원하는 팀은 속성을 채택할 수 있습니다.

응답 후 작업을 위한 Terminable 미들웨어

Terminable 미들웨어는 응답이 클라이언트에 전송된 후에 로직을 실행합니다. 이는 사용자를 차단해서는 안 되는 로깅, 분석, 정리 작업에 유용합니다.

app/Http/Middleware/CollectAnalytics.phpphp
namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesDB;
use SymfonyComponentHttpFoundationResponse;

class CollectAnalytics
{
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);  // Pass through without delay
    }

    public function terminate(Request $request, Response $response): void
    {
        // Runs after response is sent to client
        DB::table('analytics')->insert([
            'path'        => $request->path(),
            'method'      => $request->method(),
            'status_code' => $response->getStatusCode(),
            'user_id'     => $request->user()?->id,
            'ip'          => $request->ip(),
            'created_at'  => now(),
        ]);
    }
}

terminate 메서드는 원본 요청과 최종 응답을 모두 받습니다. 동일한 인스턴스가 handle()과 terminate() 모두를 처리하도록 AppServiceProvider에서 미들웨어를 싱글톤으로 등록하십시오.

프로덕션을 위한 실용적인 미들웨어 패턴

프로덕션 Laravel 애플리케이션에서 일관되게 나타나는 여러 미들웨어 패턴이 있습니다.

유지보수 모드 우회는 유지보수 중에 내부 IP가 애플리케이션에 접근할 수 있도록 허용합니다:

app/Http/Middleware/MaintenanceBypass.phpphp
class MaintenanceBypass
{
    private array $allowedIps = ['192.168.1.0/24', '10.0.0.1'];

    public function handle(Request $request, Closure $next): Response
    {
        if (app()->isDownForMaintenance()) {
            foreach ($this->allowedIps as $ip) {
                if ($request->ip() === $ip) {
                    return $next($request);
                }
            }
        }

        return $next($request);
    }
}

보안 헤더는 HSTS, 콘텐츠 보안 정책 및 기타 헤더를 모든 응답에 추가합니다:

app/Http/Middleware/SecurityHeaders.phpphp
class SecurityHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set(
            'Strict-Transport-Security',
            'max-age=31536000; includeSubDomains'
        );

        return $response;
    }
}

이 패턴들은 두 가지 주요 미들웨어 위치를 보여줍니다: 요청 전(유지보수 우회는 IP를 확인하고 잠재적으로 차단)과 응답 후(보안 헤더는 나가는 응답을 수정).

Laravel 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

소스

면접 준비를 위한 Laravel 미들웨어 패턴

  • Laravel 미들웨어는 파이프라인으로 작동합니다. 각 클래스는 요청을 처리하고, 작업을 수행하며, 앞으로 전달하거나 응답으로 단락시킵니다
  • auth 미들웨어는 가드 기반 인증으로 라우트를 보호하며, auth:guard 구문을 통해 여러 사용자 유형을 지원합니다
  • throttle 미들웨어와 명명된 RateLimiter::for() 정의를 통한 속도 제한은 사용자 컨텍스트에 기반한 계층형 접근 제어를 가능하게 합니다
  • 커스텀 미들웨어는 컨트롤러를 복잡하게 만들지 않으면서 역할 검사, 요청 정제, 보안 헤더와 같은 횡단 관심사를 처리합니다
  • 모든 미들웨어 등록은 bootstrap/app.php에서 Fluent API를 사용하며, priority가 실행 순서를 제어합니다
  • Laravel 13의 #[Middleware] 속성을 사용하면 컨트롤러에 직접 미들웨어를 선언할 수 있어 인가 규칙을 핸들러와 같은 위치에 배치할 수 있습니다
  • Terminable 미들웨어는 사용자 대면 지연 시간에 영향을 주지 않으면서 응답 후 작업(분석, 로깅)을 실행합니다
  • :param 구문을 통한 미들웨어 매개변수는 라우트 정의를 표현력 있게 유지하고 미들웨어 클래스를 다른 컨텍스트에서 재사용할 수 있게 합니다

연습을 시작하세요!

면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

오늘의 챌린지

Laravel 코드의 버그를 찾을 수 있나요

실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

Anthony Fillion-Maillet

작성자

Anthony Fillion-Maillet

SharpSkill 창업자

10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.

2026년 9월 21일 업데이트

태그

#laravel
#middleware
#authentication
#rate-limiting
#php

공유

관련 기사