인가

번역일: 2026년 6월 25일

인가

소개

Laravel은 내장된 인증 기능 외에도, 사용자가 특정 리소스에 대해 어떤 액션을 수행할 수 있는지 판단하는 인가(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입니다. 게시글을 작성한 사용자(user_id)와 현재 사용자의 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 클로저에 사용자를 주입합니다. 컨트롤러에서 인가가 필요한 액션을 실행하기 전에 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은 AuthorizationException을 자동으로 403 HTTP 응답으로 변환합니다:

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

추가 컨텍스트 전달

Gate의 인가 메서드(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를 사용하면 Gate가 반환한 전체 인가 응답 객체를 얻을 수 있습니다:

$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; } });

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 디렉터리에 저장되며, 디렉터리가 없으면 자동으로 생성됩니다:

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/Models/Policies 또는 app/Policies에 위치할 수 있습니다. 또한 Policy 클래스 이름은 모델 이름에 Policy 접미사를 붙인 형태여야 합니다. (User 모델 → UserPolicy)

Policy 자동 감지 로직을 직접 정의하고 싶다면 Gate::guessPolicyNamesUsing 메서드로 커스텀 콜백을 등록하세요. 일반적으로 AppServiceProviderboot 메서드에서 호출합니다:

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

Policy 수동 등록

Gate 파사드를 사용해 AppServiceProviderboot 메서드에서 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 속성(Attribute)을 붙여 해당 모델의 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\UserApp\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 등 게시글 관련 다양한 액션에 대한 메서드를 정의할 수 있습니다. Policy 메서드 이름은 자유롭게 지정할 수 있습니다.

Artisan 명령어에서 --model 옵션을 사용했다면 viewAny, view, create, update, delete, restore, forceDelete 메서드가 이미 포함되어 있습니다.

NOTE

모든 Policy는 Laravel 서비스 컨테이너를 통해 resolve됩니다. 따라서 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('이 게시글의 소유자가 아닙니다.'); }

Gate::allows는 여전히 불리언 값을 반환하지만, Gate::inspect로 전체 인가 응답 객체를 확인할 수 있습니다:

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

Gate::authorize를 사용하면 인가 거부 시 응답 메시지가 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 기본값을 지정하면, 비인증 사용자의 요청도 Gate와 Policy로 전달되도록 허용할 수 있습니다:

<?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

Policy 클래스에 검사하려는 ability 이름과 일치하는 메서드가 없으면 before 메서드는 호출되지 않습니다.

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를 찾아 실행합니다.

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

create처럼 모델 인스턴스가 필요하지 않은 Policy 메서드는 클래스 이름을 전달하면 됩니다. 어떤 Policy를 사용할지 결정하는 데 클래스 이름이 활용됩니다:

<?php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; class PostController extends Controller { /** * 게시글을 작성합니다. */ public function store(Request $request): RedirectResponse { if ($request->user()->cannot('create', Post::class)) { abort(403); } // 게시글 생성 처리

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

번역일: 2026년 6월 25일