Laravel 인가

1 개요[ | ]

Crystal Clear action info.png 작성 중인 문서입니다.
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::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 예외를 던집니다. 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 메소드를 사용하여 커스텀 정책 발견 콜백을 등록할 수 있습니다. 일반적으로 이 메소드는 애플리케이션의 AppServiceProviderboot 메소드에서 호출되어야 합니다:

use Illuminate\Support\Facades\Gate;
 
Gate::guessPolicyNamesUsing(function (string $modelClass) {
    // 주어진 모델에 대한 정책 클래스 이름을 반환합니다...
});
정책 수동 등록

Gate 파사드를 사용하여 애플리케이션의 AppServiceProviderboot 메소드 내에서 정책과 해당 모델을 수동으로 등록할 수 있습니다:

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 정책 클래스가 수동으로 연결됩니다.

5 정책 작성[ | ]

5.1 정책 메소드[ | ]

5.2 정책 응답[ | ]

5.3 모델 없는 메소드[ | ]

5.4 게스트 사용자[ | ]

5.5 정책 필터[ | ]

6 정책을 사용한 액션 인가[ | ]

6.1 사용자 모델을 통해[ | ]

6.2 게이트 파사드를 통해[ | ]

6.3 미들웨어를 통해[ | ]

6.4 Blade 템플릿을 통해[ | ]

6.5 추가 컨텍스트 제공[ | ]

7 인가 & Inertia[ | ]

문서 댓글 ({{ doc_comments.length }})
{{ comment.name }} {{ comment.created | snstime }}