인가

업데이트됨

번역일: 2026년 6월 25일

이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.

원문 수정
2026년 6월 20일
번역 갱신
2026년 6월 25일

인가

소개

Laravel은 인증(authentication) 기능 외에도, 사용자가 특정 리소스에 대해 어떤 액션을 수행할 수 있는지 제어하는 인가(authorization) 기능을 기본으로 제공합니다. 예를 들어, 로그인한 사용자라도 다른 사람이 작성한 게시글을 수정하거나 삭제할 수는 없어야 합니다. Laravel의 인가 기능을 사용하면 이런 권한 검사를 깔끔하고 일관되게 관리할 수 있습니다.

Laravel에서 인가를 구현하는 방법은 크게 두 가지입니다: GatePolicy. Gate와 Policy의 관계는 라우트와 컨트롤러의 관계와 비슷합니다. Gate는 클로저(익명 함수) 기반의 간단한 인가 방식이고, Policy는 특정 모델이나 리소스를 중심으로 관련 인가 로직을 하나의 클래스로 묶는 방식입니다.

애플리케이션을 개발할 때 Gate와 Policy 중 하나만 골라야 하는 것은 아닙니다. 대부분의 애플리케이션에서는 두 가지를 함께 사용하며, 그것이 자연스럽습니다. Gate는 특정 모델과 직접 관련이 없는 액션(예: 관리자 대시보드 접근)에 적합하고, Policy는 특정 모델이나 리소스에 대한 권한을 관리할 때 사용하는 것이 좋습니다.

Gate

Gate 작성하기

WARNING

Gate는 Laravel 인가 기능의 기본 개념을 익히기에 좋은 출발점입니다. 하지만 규모 있는 애플리케이션을 개발할 때는 인가 규칙을 체계적으로 정리할 수 있는 Policy 사용을 권장합니다.

Gate는 사용자가 특정 액션을 수행할 수 있는지 판단하는 클로저입니다. 일반적으로 App\Providers\AppServiceProvider 클래스의 boot 메서드 안에서 Gate 파사드를 사용해 정의합니다. Gate 클로저의 첫 번째 인자는 항상 현재 인증된 사용자 인스턴스이며, 그 뒤에 관련 Eloquent 모델 등 필요한 인자를 추가로 받을 수 있습니다.

아래 예시는 현재 사용자가 특정 App\Models\Post를 수정할 수 있는지 판단하는 Gate입니다. 사용자의 id와 게시글의 user_id를 비교해 권한을 결정합니다:

use App\Models\Post; use App\Models\User; use Illuminate\Support\Facades\Gate; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Gate::define('update-post', function (User $user, Post $post) { return $user->id === $post->user_id; }); }

클로저 대신, 클래스 콜백 배열 형태로도 Gate를 정의할 수 있습니다:

use App\Policies\PostPolicy; use Illuminate\Support\Facades\Gate; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Gate::define('update-post', [PostPolicy::class, 'update']); }

액션 인가하기

Gate를 사용해 액션을 인가하려면 Gate 파사드의 allows 또는 denies 메서드를 사용합니다. 현재 인증된 사용자를 직접 전달할 필요는 없습니다. Laravel이 자동으로 Gate 클로저에 사용자를 주입합니다. 일반적으로 컨트롤러에서 인가가 필요한 작업 직전에 호출합니다:

<?php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Gate; class PostController extends Controller { /** * 게시글을 수정합니다. */ public function update(Request $request, Post $post): RedirectResponse { if (! Gate::allows('update-post', $post)) { abort(403); } // 게시글 수정 로직... return redirect('/posts'); } }

현재 인증된 사용자가 아닌 다른 사용자의 권한을 확인하고 싶다면 forUser 메서드를 사용합니다:

if (Gate::forUser($user)->allows('update-post', $post)) { // 해당 사용자는 게시글을 수정할 수 있습니다... } if (Gate::forUser($user)->denies('update-post', $post)) { // 해당 사용자는 게시글을 수정할 수 없습니다... }

여러 액션을 한 번에 확인할 때는 any 또는 none 메서드를 사용합니다:

if (Gate::any(['update-post', 'delete-post'], $post)) { // 사용자가 게시글을 수정하거나 삭제할 수 있습니다... } if (Gate::none(['update-post', 'delete-post'], $post)) { // 사용자가 게시글을 수정하거나 삭제할 수 없습니다... }

인가 실패 시 예외 던지기

인가 실패 시 자동으로 Illuminate\Auth\Access\AuthorizationException 예외를 발생시키려면 Gate::authorize 메서드를 사용합니다. 이 예외는 Laravel이 자동으로 403 HTTP 응답으로 변환합니다:

Gate::authorize('update-post', $post); // 인가가 통과된 경우 이 아래 코드가 실행됩니다...

추가 컨텍스트 전달하기

인가 관련 메서드(allows, denies, check, any, none, authorize, can, cannot)와 Blade 디렉티브(@can, @cannot, @canany)는 두 번째 인자로 배열을 받을 수 있습니다. 배열의 각 요소는 Gate 클로저의 파라미터로 전달되어, 더 세밀한 인가 판단에 활용할 수 있습니다:

use App\Models\Category; use App\Models\User; use Illuminate\Support\Facades\Gate; Gate::define('create-post', function (User $user, Category $category, bool $pinned) { if (! $user->canPublishToGroup($category->group)) { return false; } elseif ($pinned && ! $user->canPinPosts()) { return false; } return true; }); if (Gate::check('create-post', [$category, $pinned])) { // 사용자가 게시글을 작성할 수 있습니다... }

Gate 응답

지금까지는 Gate가 단순한 불리언 값을 반환하는 경우만 살펴봤습니다. 하지만 때로는 오류 메시지 등 더 자세한 응답을 반환하고 싶을 수 있습니다. 이런 경우 Gate 클로저에서 Illuminate\Auth\Access\Response 인스턴스를 반환합니다:

use App\Models\User; use Illuminate\Auth\Access\Response; use Illuminate\Support\Facades\Gate; Gate::define('edit-settings', function (User $user) { return $user->isAdmin ? Response::allow() : Response::deny('관리자만 설정을 변경할 수 있습니다.'); });

Gate::allows는 여전히 불리언 값을 반환합니다. 응답 객체 전체를 받고 싶다면 Gate::inspect를 사용합니다:

$response = Gate::inspect('edit-settings'); if ($response->allowed()) { // 인가가 통과되었습니다... } else { echo $response->message(); }

Gate::authorize를 사용할 경우, 인가 실패 시 응답에 담긴 오류 메시지가 HTTP 응답에 함께 전달됩니다:

Gate::authorize('edit-settings'); // 인가가 통과된 경우 이 아래 코드가 실행됩니다...

HTTP 응답 상태 코드 커스터마이징

Gate에서 액션이 거부되면 기본적으로 403 HTTP 응답이 반환됩니다. 다른 상태 코드를 반환하고 싶다면 Illuminate\Auth\Access\ResponsedenyWithStatus 정적 생성자를 사용합니다:

use App\Models\User; use Illuminate\Auth\Access\Response; use Illuminate\Support\Facades\Gate; Gate::define('edit-settings', function (User $user) { return $user->isAdmin ? Response::allow() : Response::denyWithStatus(404); });

리소스의 존재 여부를 숨기기 위해 404로 응답하는 패턴은 웹 애플리케이션에서 매우 흔합니다. 이를 위한 편의 메서드로 denyAsNotFound를 사용할 수 있습니다:

use App\Models\User; use Illuminate\Auth\Access\Response; use Illuminate\Support\Facades\Gate; Gate::define('edit-settings', function (User $user) { return $user->isAdmin ? Response::allow() : Response::denyAsNotFound(); });

Gate 검사 가로채기

특정 사용자(예: 관리자)에게 모든 권한을 부여하고 싶다면 before 메서드로 다른 모든 인가 검사보다 먼저 실행되는 클로저를 정의합니다:

use App\Models\User; use Illuminate\Support\Facades\Gate; Gate::before(function (User $user, string $ability) { if ($user->isAdministrator()) { return true; } });

before 클로저가 null이 아닌 값을 반환하면, 그 값이 인가 검사의 최종 결과가 됩니다.

모든 인가 검사가 끝난 후 실행되는 클로저는 after 메서드로 정의합니다:

use App\Models\User; Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) { if ($user->isAdministrator()) { return true; } });

NOTE

after 클로저의 반환 값은 Gate 또는 Policy가 null을 반환한 경우에만 인가 결과를 덮어씁니다. 명시적인 true 또는 false가 이미 반환된 경우에는 after의 반환 값이 적용되지 않습니다.

인라인 인가

별도의 Gate를 정의하지 않고 즉석에서 인가 검사를 수행하고 싶다면 Gate::allowIfGate::denyIf를 사용합니다. 인라인 인가는 before/after 훅을 실행하지 않습니다:

use App\Models\User; use Illuminate\Support\Facades\Gate; Gate::allowIf(fn (User $user) => $user->isAdministrator()); Gate::denyIf(fn (User $user) => $user->banned());

인가가 거부되거나 현재 인증된 사용자가 없는 경우, Laravel은 자동으로 Illuminate\Auth\Access\AuthorizationException 예외를 발생시키며, 이는 403 HTTP 응답으로 변환됩니다.

Policy 생성하기

Policy 생성 명령어

Policy는 특정 모델이나 리소스와 관련된 인가 로직을 하나의 클래스로 모아두는 방식입니다. 예를 들어 블로그 애플리케이션이라면 App\Models\Post 모델에 대응하는 App\Policies\PostPolicy를 만들어 게시글 생성, 수정 등의 권한을 관리할 수 있습니다.

make:policy Artisan 명령어로 Policy 클래스를 생성합니다. 생성된 파일은 app/Policies 디렉터리에 저장되며, 해당 디렉터리가 없으면 Laravel이 자동으로 만들어 줍니다:

php artisan make:policy PostPolicy

위 명령어는 빈 Policy 클래스를 생성합니다. 리소스의 조회, 생성, 수정, 삭제에 해당하는 예시 메서드가 포함된 클래스를 생성하려면 --model 옵션을 사용합니다:

php artisan make:policy PostPolicy --model=Post

Policy 등록하기

자동 Policy 탐색

Laravel은 모델과 Policy가 표준 네이밍 컨벤션을 따르는 경우 자동으로 Policy를 탐색합니다. 구체적으로, Policy는 모델이 위치한 디렉터리 또는 그 상위 디렉터리의 Policies 폴더 안에 있어야 합니다. 예를 들어 모델이 app/Models에 있다면 Policy는 app/Policies에 위치하면 됩니다. 이 경우 Laravel은 app/Models/Policies, 그 다음으로 app/Policies 순서로 Policy를 탐색합니다. 또한 Policy 클래스 이름은 모델 이름 뒤에 Policy를 붙인 형태여야 합니다(예: User 모델 → UserPolicy).

Policy 탐색 로직을 직접 정의하려면 Gate::guessPolicyNamesUsing 메서드로 커스텀 콜백을 등록합니다. 이 메서드는 AppServiceProviderboot 메서드에서 호출하는 것이 일반적입니다:

use Illuminate\Support\Facades\Gate; Gate::guessPolicyNamesUsing(function (string $modelClass) { // 모델 클래스에 해당하는 Policy 클래스 이름을 반환합니다... });

수동으로 Policy 등록하기

AppServiceProviderboot 메서드에서 Gate 파사드를 사용해 모델과 Policy를 직접 매핑할 수 있습니다:

use App\Models\Order; use App\Policies\OrderPolicy; use Illuminate\Support\Facades\Gate; /** * 애플리케이션 서비스를 부트스트랩합니다. */ public function boot(): void { Gate::policy(Order::class, OrderPolicy::class); }

또는 모델 클래스에 UsePolicy 어트리뷰트를 붙여 해당 모델의 Policy를 지정할 수도 있습니다:

<?php namespace App\Models; use App\Policies\OrderPolicy; use Illuminate\Database\Eloquent\Attributes\UsePolicy; use Illuminate\Database\Eloquent\Model; #[UsePolicy(OrderPolicy::class)] class Order extends Model { // }

Policy 작성하기

Policy 메서드

Policy 클래스가 등록되면, 인가할 각 액션에 해당하는 메서드를 추가합니다. 예를 들어, PostPolicyupdate 메서드를 정의해 특정 사용자가 특정 App\Models\Post를 수정할 수 있는지 판단해 보겠습니다.

update 메서드는 UserPost 인스턴스를 인자로 받아, 사용자가 해당 게시글을 수정할 수 있으면 true, 없으면 false를 반환해야 합니다:

<?php namespace App\Policies; use App\Models\Post; use App\Models\User; class PostPolicy { /** * 해당 사용자가 게시글을 수정할 수 있는지 판단합니다. */ public function update(User $user, Post $post): bool { return $user->id === $post->user_id; } }

필요에 따라 view, delete 등 다양한 액션에 해당하는 메서드를 자유롭게 추가할 수 있습니다. 메서드 이름은 원하는 대로 지정해도 됩니다.

--model 옵션으로 Policy를 생성했다면, viewAny, view, create, update, delete, restore, forceDelete 액션에 해당하는 메서드가 이미 포함되어 있습니다.

NOTE

모든 Policy는 Laravel 서비스 컨테이너를 통해 해석됩니다. 따라서 Policy 생성자에 필요한 의존성을 타입 힌트로 선언하면 자동으로 주입됩니다.

Policy 응답

단순한 불리언 값 외에 오류 메시지 등 더 자세한 응답을 반환하고 싶다면, Policy 메서드에서 Illuminate\Auth\Access\Response 인스턴스를 반환합니다:

use App\Models\Post; use App\Models\User; use Illuminate\Auth\Access\Response; /** * 해당 사용자가 게시글을 수정할 수 있는지 판단합니다. */ public function update(User $user, Post $post): Response { return $user->id === $post->user_id ? Response::allow() : Response::deny('이 게시글의 작성자가 아닙니다.'); }

Policy에서 Response를 반환해도 Gate::allows는 여전히 불리언을 반환합니다. 응답 전체를 받고 싶다면 Gate::inspect를 사용합니다:

use Illuminate\Support\Facades\Gate; $response = Gate::inspect('update', $post); if ($response->allowed()) { // 인가가 통과되었습니다... } else { echo $response->message(); }

Gate::authorize를 사용하면 인가 실패 시 Policy 응답의 오류 메시지가 HTTP 응답에 포함됩니다:

Gate::authorize('update', $post); // 인가가 통과된 경우 이 아래 코드가 실행됩니다...

HTTP 응답 상태 코드 커스터마이징

Policy 메서드에서 액션이 거부되면 기본적으로 403 HTTP 응답이 반환됩니다. 다른 상태 코드를 반환하고 싶다면 denyWithStatus를 사용합니다:

use App\Models\Post; use App\Models\User; use Illuminate\Auth\Access\Response; /** * 해당 사용자가 게시글을 수정할 수 있는지 판단합니다. */ public function update(User $user, Post $post): Response { return $user->id === $post->user_id ? Response::allow() : Response::denyWithStatus(404); }

404로 리소스의 존재를 숨기는 패턴에는 편의 메서드인 denyAsNotFound를 사용할 수 있습니다:

use App\Models\Post; use App\Models\User; use Illuminate\Auth\Access\Response; /** * 해당 사용자가 게시글을 수정할 수 있는지 판단합니다. */ public function update(User $user, Post $post): Response { return $user->id === $post->user_id ? Response::allow() : Response::denyAsNotFound(); }

모델 인스턴스가 필요 없는 메서드

일부 Policy 메서드는 인증된 사용자 인스턴스만 받으면 됩니다. create 액션이 대표적인 예입니다. 새 게시글을 작성할 수 있는 권한 여부를 확인할 때는 특정 게시글 인스턴스가 필요하지 않기 때문입니다:

/** * 해당 사용자가 게시글을 작성할 수 있는지 판단합니다. */ public function create(User $user): bool { return $user->role == 'writer'; }

비로그인 사용자(게스트)

기본적으로 Gate와 Policy는 인증되지 않은 사용자(게스트)의 요청에 대해 자동으로 false를 반환합니다. 하지만 게스트에게도 인가 검사가 통과될 수 있도록 허용하려면, 사용자 인자를 옵셔널 타입 힌트(?User)로 선언하거나 기본값으로 null을 설정하면 됩니다:

<?php namespace App\Policies; use App\Models\Post; use App\Models\User; class PostPolicy { /** * 해당 사용자가 게시글을 수정할 수 있는지 판단합니다. */ public function update(?User $user, Post $post): bool { return $user?->id === $post->user_id; } }

Policy 필터

특정 사용자에게 Policy 내의 모든 액션을 허용하고 싶다면 Policy 클래스에 before 메서드를 정의합니다. before 메서드는 다른 Policy 메서드보다 먼저 실행되므로, 실제 메서드가 호출되기 전에 액션을 인가할 수 있습니다. 관리자에게 모든 권한을 부여하는 데 주로 활용됩니다:

use App\Models\User; /** * 사전 인가 검사를 수행합니다. */ public function before(User $user, string $ability): bool|null { if ($user->isAdministrator()) { return true; } return null; }

특정 사용자 유형의 모든 인가 요청을 거부하려면 before 메서드에서 false를 반환합니다. null을 반환하면 인가 검사는 해당 Policy 메서드로 넘어갑니다.

WARNING

before 메서드는 Policy 클래스 안에 확인하려는 ability 이름과 일치하는 메서드가 있는 경우에만 호출됩니다.

Policy를 사용한 액션 인가

User 모델을 통해

Laravel의 App\Models\User 모델에는 cancannot 두 가지 인가 메서드가 포함되어 있습니다. 두 메서드 모두 인가할 액션 이름과 관련 모델을 인자로 받습니다. 컨트롤러 메서드에서 일반적으로 다음과 같이 사용합니다:

<?php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; class PostController extends Controller { /** * 게시글을 수정합니다. */ public function update(Request $request, Post $post): RedirectResponse { if ($request->user()->cannot('update', $post)) { abort(403); } // 게시글 수정 로직... return redirect('/posts'); } }

해당 모델에 Policy가 등록되어 있으면 can 메서드는 자동으로 적절한 Policy를 호출합니다. 등록된 Policy가 없으면 해당 액션 이름과 일치하는 클로저 기반 Gate를 호출하려 시도합니다.

모델 인스턴스가 필요 없는 액션

`

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 6월 25일