1 개요[ | ]
- Authorization
- 인가
https://laravel.com/docs/11.x/authorization
2 소개[ | ]
Laravel은 빌트인 인증 서비스 외에도 주어진 리소스에 대해 사용자의 액션을 인가할 수 있는 간단한 방법을 제공합니다. 예를 들어, 사용자가 인증되었다 하더라도 애플리케이션에서 관리하는 특정 Eloquent 모델이나 데이터베이스 레코드를 업데이트하거나 삭제할 권한이 없을 수도 있습니다. Laravel의 인가 기능은 이러한 유형의 인가 확인을 관리하는 쉬운 방법을 제공합니다.
Laravel은 주로 두 가지 방식으로 액션을 인가합니다: 게이트(gate)와 정책(policy). 게이트와 정책을 라우트와 컨트롤러처럼 생각할 수 있습니다. 게이트는 간단한 클로저 기반 접근 방식을 제공하는 반면, 정책은 특정 모델이나 리소스를 중심으로 로직을 그룹화합니다. 이 문서에서는 먼저 게이트를 알아본 다음 정책을 살펴보겠습니다.
애플리케이션을 구축할 때 게이트만 사용하거나 정책만 사용하는 것을 선택할 필요는 없습니다. 대부분의 애플리케이션은 게이트와 정책이 혼합된 형태를 포함할 가능성이 높으며, 이는 전혀 문제가 되지 않습니다! 게이트는 관리자 대시보드를 보는 것과 같이 특정 모델이나 리소스와 관련이 없는 액션에 가장 적합합니다. 반면, 정책은 특정 모델이나 리소스에 대한 액션을 인가하고자 할 때 사용해야 합니다.
3 게이트[ | ]
3.1 게이트 작성[ | ]
Warning
게이트는 Laravel의 인가 기능의 기초를 배우는 훌륭한 방법입니다. 그러나 견고한 Laravel 애플리케이션을 구축할 때는 인가 규칙을 조직화하기 위해 정책을 사용하는 것을 고려해야 합니다.
게이트는 단순히 사용자가 주어진 작업을 수행할 권한이 있는지를 결정하는 클로저입니다. 일반적으로 게이트는 Gate
파사드를 사용하여 App\Providers\AppServiceProvider
클래스의 boot
메소드 내에서 정의됩니다. 게이트는 항상 사용자 인스턴스를 첫 번째 인수로 받고, 선택적으로 관련 있는 Eloquent 모델과 같은 추가 인수를 받을 수 있습니다.
이 예제에서는 사용자가 주어진 App\Models\Post
모델을 업데이트할 수 있는지 여부를 결정하는 게이트를 정의합니다. 게이트는 사용자의 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;
});
}
컨트롤러와 마찬가지로, 게이트도 클래스 콜백 배열을 사용하여 정의할 수 있습니다:
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Gate::define('update-post', [PostPolicy::class, 'update']);
}
3.2 액션 인가[ | ]
게이트를 사용하여 액션를 승인하려면, Gate
파사드가 제공하는 allows
또는 denies
메소드를 사용해야 합니다. 현재 인증된 사용자를 이 메소드에 전달할 필요는 없습니다. Laravel이 게이트 클로저로 사용자를 자동으로 전달하기 때문입니다. 인가가 필요한 액션을 수행하기 전에 애플리케이션의 컨트롤러에서 게이트 인가 메소드를 호출하는 것이 일반적입니다:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
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');
}
}
현재 인증된 사용자 외에 다른 사용자가 액션을 수행할 수 있는지 확인하려면 Gate
파사드의 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
메소드를 사용할 수 있습니다. AuthorizationException
인스턴스는 Laravel에 의해 자동으로 403 HTTP 응답으로 변환됩니다:
Gate::authorize('update-post', $post);
// 액션이 인가되었습니다...
- 추가 컨텍스트 제공
인가 기능을 위한 게이트 메소드(allow
, deny
, check
, any
, none
, authorize
, can
, cannot
)와 인가 Blade 지시문(@can
, @cannot
, @canany
)은 두 번째 인수로 배열을 받을 수 있습니다. 이 배열 요소는 게이트 클로저에 매개변수로 전달되며, 인가 결정을 내릴 때 추가 컨텍스트로 사용할 수 있습니다:
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])) {
// 사용자가 포스트를 생성할 수 있습니다...
}
3.3 게이트 응답[ | ]
지금까지는, 단순한 불리언 값을 반환하는 게이트만 살펴보았습니다. 하지만 때로는 오류 메시지를 포함한 더 자세한 응답을 반환하고 싶을 때가 있습니다. 이를 위해 게이트에서 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('You must be an administrator.');
});
게이트에서 인가 응답을 반환할 때에도 Gate::allows
메소드는 여전히 단순한 불리언 값을 반환하지만, Gate::inspect
메소드를 사용하여 게이트에서 반환된 전체 인가 응답을 얻을 수 있습니다:
$response = Gate::inspect('edit-settings');
if ($response->allowed()) {
// 액션이 허가되었습니다...
} else {
echo $response->message();
}
게이트에서 인가되지 않은 액션에 대해 AuthorizationException
을 던지는 Gate::authorize
메소드를 사용할 때, 인가 응답에서 제공한 오류 메시지는 HTTP 응답으로 전달됩니다:
Gate::authorize('edit-settings');
// 액션이 인가되었습니다...
- HTTP 응답 상태 커스터마이징
게이트를 통해 액션이 거부될 때 403 HTTP 응답이 반환되지만, 때로는 대체 HTTP 상태 코드를 반환하는 것이 유용할 수 있습니다. 실패한 인가 확인에 대한 HTTP 상태 코드를 커스터마이징하려면 Illuminate\Auth\Access\Response
클래스의 denyWithStatus
정적 생성자를 사용할 수 있습니다:
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();
});
3.4 게이트 체크 가로채기[ | ]
때로는 특정 사용자에게 모든 권한을 부여하고자 할 때가 있습니다. 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;
}
});
before
메소드와 마찬가지로, after
클로저가 null이 아닌 결과를 반환하면, 그 결과가 권한 검사 결과로 간주됩니다.
3.5 인라인 인가[ | ]
때로는, 특정 작업에 해당하는 전용 게이트를 작성하지 않고 현재 인증된 사용자가 주어진 작업을 수행할 권한이 있는지 확인하고 싶을 때가 있습니다. Laravel은 Gate::allowIf
와 Gate::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
예외를 던집니다. AuthorizationException
인스턴스는 Laravel의 예외 처리기에 의해 자동으로 403 HTTP 응답으로 변환됩니다.
4 정책 생성[ | ]
4.1 정책 생성하기[ | ]
정책은 특정 모델이나 리소스와 관련된 인가 로직을 구성하는 클래스입니다. 예를 들어, 애플리케이션이 블로그라면, App\Models\Post
모델과 해당 모델에 대한 사용자 액션(예: 게시물 작성 또는 업데이트)을 승인하는 App\Policies\PostPolicy
가 있을 수 있습니다.
make:policy
Artisan 명령어를 사용하여 정책을 생성할 수 있습니다. 생성된 정책은 app/Policies
디렉토리에 배치됩니다. 이 디렉토리가 없으면 Laravel이 생성합니다:
php artisan make:policy PostPolicy
make:policy
명령어는 빈 정책 클래스를 생성합니다. 리소스 보기, 작성, 업데이트, 삭제와 관련된 예제 정책 메소드가 포함된 클래스를 생성하려면 명령을 실행할 때 --model
옵션을 제공할 수 있습니다:
php artisan make:policy PostPolicy --model=Post
4.2 정책 등록하기[ | ]
- 정책 발견
기본적으로, Laravel은 모델과 정책이 표준 Laravel 명명 규칙을 따르는 한 자동으로 정책을 발견합니다. 구체적으로 정책은, 모델을 포함하는 디렉토리 또는 그 상위 디렉토리에 있는 Policies
디렉토리에 있어야 합니다. 예를 들어, 모델이 app/Models
디렉토리에 배치된 경우, 정책은 app/Policies
디렉토리에 배치될 수 있습니다. 이러한 상황에서 Laravel은 먼저 app/Models/Policies
를 확인한 다음 app/Policies
를 확인합니다. 또한, 정책 이름은 모델 이름과 일치하고 Policy
접미어를 가져야 합니다. 따라서 User
모델은 UserPolicy
정책 클래스와 대응됩니다.
자신만의 정책 발견 로직을 정의하고 싶다면 Gate::guessPolicyNamesUsing
메소드를 사용하여 커스텀 정책 발견 콜백을 등록할 수 있습니다. 일반적으로 이 메소드는 애플리케이션의 AppServiceProvider
의 boot
메소드에서 호출되어야 합니다:
use Illuminate\Support\Facades\Gate;
Gate::guessPolicyNamesUsing(function (string $modelClass) {
// 주어진 모델에 대한 정책 클래스 이름을 반환합니다...
});
- 정책 수동 등록
Gate
파사드를 사용하여 애플리케이션의 AppServiceProvider
의 boot
메소드 내에서 정책과 해당 모델을 수동으로 등록할 수 있습니다:
use App\Models\Order;
use App\Policies\OrderPolicy;
use Illuminate\Support\Facades\Gate;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Gate::policy(Order::class, OrderPolicy::class);
}
이렇게 하면 Order 모델과 OrderPolicy 정책 클래스가 수동으로 연결됩니다.