본문으로 건너뛰기
버전: 8.x

미들웨어 (Middleware)

소개

미들웨어는 여러분의 애플리케이션에 들어오는 HTTP 요청을 검사하고 필터링하는 데 편리한 방법을 제공합니다. 예를 들어, 라라벨에는 사용자가 인증되었는지를 확인하는 미들웨어가 기본으로 포함되어 있습니다. 만약 사용자가 인증되지 않았다면, 미들웨어는 사용자를 애플리케이션의 로그인 화면으로 리다이렉트합니다. 반대로, 사용자가 인증된 경우에는 요청을 애플리케이션 내부로 더 깊게 전달합니다.

인증 외에도 다양한 작업을 수행하는 추가 미들웨어를 직접 작성할 수 있습니다. 예를 들어, 로그를 남기는 미들웨어는 애플리케이션에 들어오는 모든 요청을 기록할 수 있습니다. 라라벨 프레임워크에는 인증, CSRF 보호 등 여러 미들웨어가 기본으로 포함되어 있으며, 이 모든 미들웨어는 app/Http/Middleware 디렉터리에 위치합니다.

미들웨어 정의하기

새로운 미들웨어를 생성하려면 make:middleware Artisan 명령어를 사용합니다.

php artisan make:middleware EnsureTokenIsValid

이 명령어를 실행하면 app/Http/Middleware 디렉터리 내에 새로운 EnsureTokenIsValid 클래스가 생성됩니다. 이 미들웨어에서는 전달받은 token 입력값이 지정한 값과 일치하는 경우에만 해당 라우트에 접근할 수 있도록 허용합니다. 그렇지 않은 경우, 사용자를 home URI로 리다이렉트합니다.

<?php

namespace App\Http\Middleware;

use Closure;

class EnsureTokenIsValid
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('home');
}

return $next($request);
}
}

위 예제에서 볼 수 있듯이, 전달받은 token이 우리가 지정한 시크릿 토큰과 일치하지 않으면 미들웨어는 HTTP 리다이렉트를 클라이언트에 반환합니다. 일치하는 경우에는 요청이 애플리케이션 내부로 더 깊게 전달됩니다. 미들웨어를 "통과"시켜 다음 단계로 요청을 전달하려면 $next 콜백에 $request를 전달해 호출해야 합니다.

미들웨어는 HTTP 요청이 애플리케이션에 도달하기 전에 거쳐야 하는 "여러 개의 계층"으로 생각하면 이해하기 쉽습니다. 각 계층은 요청을 검사하고, 필요하다면 요청 자체를 거부할 수도 있습니다.

[!TIP] 모든 미들웨어는 서비스 컨테이너를 통해 resolve(해결)됩니다. 따라서 미들웨어 생성자에 필요한 의존성을 타입힌트로 지정해 사용할 수 있습니다.

미들웨어와 응답

물론, 미들웨어는 요청을 애플리케이션 내부로 넘기기 전이나 넘긴 후에 작업을 수행할 수 있습니다. 아래 예시의 미들웨어는 요청이 애플리케이션에서 처리되기 전에 작업을 수행합니다.

<?php

namespace App\Http\Middleware;

use Closure;

class BeforeMiddleware
{
public function handle($request, Closure $next)
{
// 작업 수행

return $next($request);
}
}

반대로, 다음 미들웨어는 요청이 애플리케이션에서 처리된 후에 작업을 수행합니다.

<?php

namespace App\Http\Middleware;

use Closure;

class AfterMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);

// 작업 수행

return $response;
}
}

미들웨어 등록하기

전역 미들웨어

모든 HTTP 요청에 대해 미들웨어를 항상 실행하고 싶다면, app/Http/Kernel.php 클래스의 $middleware 속성에 해당 미들웨어 클래스를 추가하면 됩니다.

미들웨어를 라우트에 할당하기

특정 라우트에만 미들웨어를 적용하려면 먼저 애플리케이션의 app/Http/Kernel.php 파일에서 미들웨어에 키를 할당해야 합니다. 기본적으로 라라벨이 포함하는 미들웨어에 대해 $routeMiddleware 속성에 항목이 등록되어 있습니다. 이 목록에 직접 미들웨어를 추가하고 원하는 키로 정의할 수 있습니다.

// Within App\Http\Kernel class...

protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];

HTTP 커널에서 미들웨어를 정의한 후, 라우트에서 middleware 메서드로 미들웨어를 할당할 수 있습니다.

Route::get('/profile', function () {
//
})->middleware('auth');

여러 개의 미들웨어를 한 라우트에 할당하려면 middleware 메서드에 미들웨어 이름 배열을 전달하면 됩니다.

Route::get('/', function () {
//
})->middleware(['first', 'second']);

미들웨어 할당 시, 클래스명을 직접 사용해서 할당하는 것도 가능합니다.

use App\Http\Middleware\EnsureTokenIsValid;

Route::get('/profile', function () {
//
})->middleware(EnsureTokenIsValid::class);

미들웨어 제외하기

여러 라우트를 그룹으로 묶어 미들웨어를 할당했을 때, 그룹에 속한 특정 라우트만 미들웨어의 적용을 제외하고 싶을 때가 있습니다. 이럴 때는 withoutMiddleware 메서드를 사용하면 됩니다.

use App\Http\Middleware\EnsureTokenIsValid;

Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
//
});

Route::get('/profile', function () {
//
})->withoutMiddleware([EnsureTokenIsValid::class]);
});

또한, 라우트 그룹 전체에서 특정 미들웨어 집합을 제외할 수도 있습니다.

use App\Http\Middleware\EnsureTokenIsValid;

Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
//
});
});

withoutMiddleware 메서드는 라우트 미들웨어만 제거 가능하며, 전역 미들웨어에는 적용되지 않습니다.

미들웨어 그룹

여러 개의 미들웨어를 하나의 키 아래 묶어서 라우트에 더 쉽게 할당하고 싶을 때가 있습니다. 이런 경우 HTTP 커널의 $middlewareGroups 속성을 사용해 미들웨어 그룹을 만들 수 있습니다.

라라벨에서는 기본적으로 webapi라는 미들웨어 그룹이 제공됩니다. 이 그룹들은 웹 라우트와 API 라우트에 자주 사용되는 미들웨어들을 미리 모아둔 것입니다. 이 미들웨어 그룹들은 애플리케이션의 App\Providers\RouteServiceProvider 서비스 프로바이더에서 해당 webapi 라우트 파일에 자동으로 적용됩니다.

/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

미들웨어 그룹 역시 개별 미들웨어와 마찬가지로 라우트와 컨트롤러 액션에 할당할 수 있습니다. 그룹을 이용하면 많은 미들웨어를 한 번에 라우트에 할당할 수 있기 때문에 관리가 훨씬 편리해집니다.

Route::get('/', function () {
//
})->middleware('web');

Route::middleware(['web'])->group(function () {
//
});

[!TIP] webapi 미들웨어 그룹은 App\Providers\RouteServiceProvider가 알아서 각 애플리케이션의 routes/web.php, routes/api.php 파일에 자동 적용합니다.

미들웨어 정렬하기

가끔 특정 미들웨어가 반드시 정해진 순서대로 실행되어야 하지만, 라우트에 할당할 때 그 순서를 직접 지정할 수 없는 경우가 있습니다. 이럴 때는 app/Http/Kernel.php 파일의 $middlewarePriority 속성을 사용해 미들웨어의 우선순위를 지정할 수 있습니다. 이 속성은 기본적으로 HTTP 커널에 정의되어 있지 않을 수 있기 때문에, 필요하다면 아래 예시를 복사해 추가할 수 있습니다.

/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var string[]
*/
protected $middlewarePriority = [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];

미들웨어 파라미터

미들웨어는 추가적인 파라미터를 받을 수도 있습니다. 예를 들어, 인증된 사용자가 특정 "role"을 가지고 있는지 확인해야 하는 경우, EnsureUserHasRole이라는 미들웨어를 만들어 역할 이름(role name)을 추가 인수로 받을 수 있습니다.

추가 미들웨어 파라미터는 $next 인자 뒤에 전달됩니다.

<?php

namespace App\Http\Middleware;

use Closure;

class EnsureUserHasRole
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $role
* @return mixed
*/
public function handle($request, Closure $next, $role)
{
if (! $request->user()->hasRole($role)) {
// Redirect...
}

return $next($request);
}

}

미들웨어 파라미터는 라우트 정의 시 :(콜론)으로 미들웨어 이름과 파라미터를 구분해 지정할 수 있습니다. 여러 파라미터가 필요한 경우 쉼표로 구분합니다.

Route::put('/post/{id}', function ($id) {
//
})->middleware('role:editor');

종료 가능한 미들웨어

경우에 따라 미들웨어가 HTTP 응답이 브라우저로 전송된 에 추가 작업을 해야 할 수도 있습니다. 미들웨어에 terminate 메서드를 정의해두고, 웹 서버가 FastCGI로 동작 중이라면 응답이 브라우저에 전송된 후 자동으로 terminate 메서드가 호출됩니다.

<?php

namespace Illuminate\Session\Middleware;

use Closure;

class TerminatingMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}

/**
* Handle tasks after the response has been sent to the browser.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
// ...
}
}

terminate 메서드는 요청 객체와 응답 객체를 모두 전달받도록 해야 합니다. 종료 가능한 미들웨어를 정의했다면, 해당 미들웨어를 app/Http/Kernel.php 의 라우트 또는 전역 미들웨어 목록에 등록해 주어야 합니다.

라라벨이 미들웨어의 terminate 메서드를 호출할 때는 서비스 컨테이너에서 미들웨어의 새로운 인스턴스를 resolve합니다. 만약 handleterminate 메서드가 동일한 미들웨어 인스턴스에서 호출되기를 원한다면, 컨테이너의 singleton 메서드를 사용해 미들웨어를 싱글톤으로 등록하면 됩니다. 일반적으로는 AppServiceProviderregister 메서드에서 등록합니다.

use App\Http\Middleware\TerminatingMiddleware;

/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->singleton(TerminatingMiddleware::class);
}