Мини-блог на Laravel, часть 9. Защита маршрутов создания, редактирования и удаления

27.09.2020

Теги: LaravelMySQLPHPWeb-разработкаАвторизацияАутентификацияБазаДанныхБлогПользовательПраваДоступаПрактикаФреймворк

Чтобы предоставить доступ к определённым роутам только аутентифицированным пользователям, можно использовать посредник (middleware). Laravel поставляется с посредником auth, который определён в Illuminate\Auth\Middleware\Authenticate. Когда посредник определяет, что пользователь не аутентифицирован, он перенаправляет его на роут с именем login. Мы можем изменить это поведение в методе redirectTo класса app/Http/Middleware/Authenticate.php.

<?php
namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware {
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    protected function redirectTo($request) {
        if (! $request->expectsJson()) {
            return route('login');
        }
    }
}

Защита маршрутов

Давайте защитим наши маршруты создания, редактирования и удаления поста.

<?php
use Illuminate\Support\Facades\Route;

Route::get('/', 'PostController@index')->name('blog.index');

Route::get('post/index', 'PostController@index')->name('post.index');
Route::get('post/search', 'PostController@search')->name('post.search');
Route::get('post/create', 'PostController@create')->name('post.create')->middleware('auth');
Route::post('post/store', 'PostController@store')->name('post.store')->middleware('auth');
Route::get('post/show/{id}', 'PostController@show')->name('post.show');
Route::get('post/edit/{id}', 'PostController@edit')->name('post.edit')->middleware('auth');
Route::patch('post/update/{id}', 'PostController@update')->name('post.update')->middleware('auth');
Route::delete('post/destroy/{id}', 'PostController@destroy')->name('post.destroy')->middleware('auth');

Auth::routes();

Второй способ защитить маршруты — добавить посредник auth в конструктор контроллера.

class PostController extends Controller {
    /* ... */
    public function __construct() {
        // не аутентифицированнные пользователи могут только просматривать
        $this->middleware('auth')->except('index', 'show', 'search');
    }
    /* ... */
}

Определить, что пользователь уже вошёл в систему, можно с помощью методов check() и guest() фасада Auth или хелпера auth().

use Illuminate\Support\Facades\Auth;
if (Auth::check()) {
    // Пользователь вошёл в систему...
}
if (auth()->guest()) {
    // Пользователь не вошёл в систему...
}

Права доступа

Теперь добавлять, редактировать и удалять посты блога могут только аутентифицированнные пользователи. Но плохо, что все пользователи могут видеть кнопки «Редактировать» и «Удалить». Давайте это исправим — для этого редактируем шаблон show.blade.php.

@extends('layouts.site', ['title' => $post->title])

@section('content')
    <div class="row">
        <div class="col-12">
            <div class="card mt-4 mb-4">
                <div class="card-header">
                    <h1>{{ $post->title }}</h1>
                </div>
                <div class="card-body">
                    <img src="{{ $post->image ?? asset('img/default.jpg') }}" alt="" class="img-fluid">
                    <p class="mt-3 mb-0">{{ $post->body }}</p>
                </div>
                <div class="card-footer">
                    <div class="clearfix">
                        <span class="float-left">
                            Автор: {{ $post->author }}
                            <br>
                            Дата: {{ date_format($post->created_at, 'd.m.Y H:i') }}
                        </span>
                        <span class="float-right">
                        @auth <!-- Только аутентифицированные пользователи могут редактировать и удалять -->
                            @if (auth()->id() == $post->author_id) <!-- …причем, только свои посты блога -->
                                <a href="{{ route('post.edit', ['id' => $post->post_id]) }}"
                                   class="btn btn-dark mr-2">Редактировать</a>
                                <!-- Форма для удаления поста -->
                                <form action="{{ route('post.destroy', ['id' => $post->post_id]) }}"
                                      method="post" onsubmit="return confirm('Удалить этот пост?')"
                                      class="d-inline">
                                    @csrf
                                    @method('DELETE')
                                    <input type="submit" class="btn btn-danger" value="Удалить">
                                </form>
                            @endif
                        @endauth
                        </span>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Но это еще не все — посты блога может редактировать и удалять только автор. Так что вносим изменения в методы контроллера.

<?php
namespace App\Http\Controllers;

use App\Http\Requests\PostRequest;
use App\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;

class PostController extends Controller {
    /* ... */
    public function __construct() {
        // не аутентифицированнные пользователи могут только просматривать
        $this->middleware('auth')->except('index', 'show', 'search');
    }
    /* ... */
    public function edit($id) {
        $post = Post::findOrFail($id);
        // пользователь может редактировать только свои посты
        if (Auth::id() != $post->author_id) {
            return redirect()
                ->route('post.index')
                ->withErrors('Вы можете редактировать только свои посты');
        }
        return view('posts.edit', compact('post'));
    }
    /* ... */
    public function update(PostRequest $request, $id) {
        $post = Post::findOrFail($id);
        // пользователь может редактировать только свои посты
        if (Auth::id() != $post->author_id) {
            return redirect()
                ->route('post.index')
                ->withErrors('Вы можете редактировать только свои посты');
        }
        $post->title = $request->input('title');
        $post->excerpt = $request->input('excerpt');
        $post->body = $request->input('body');
        // если надо удалить старое изображение
        if ($request->input('remove')) {
            $this->removeImage($post);
        }
        // если было загружено новое изображение
        $this->uploadImage($request, $post);
        // все готово, можно сохранять пост в БД
        $post->update();
        return redirect()
            ->route('post.show', compact('id'))
            ->with('success', 'Пост успешно отредактирован');
    }
    /* ... */
    public function destroy($id) {
        $post = Post::findOrFail($id);
        // пользователь может удалять только свои посты
        if (Auth::id() != $post->author_id) {
            return redirect()
                ->route('post.index')
                ->withErrors('Вы можете удалять только свои посты');
        }
        $this->removeImage($post);
        $post->delete();
        return redirect()
            ->route('post.index')
            ->with('success', 'Пост был успешно удален');
    }
}

И еще один момент — будем устанавливать идентификатор пользователя в поле author_id при добавлении нового поста.

class PostController extends Controller {
    /* ... */
    public function store(PostRequest $request) {
        $post = new Post();
        // автор поста — текущий пользователь
        $post->author_id = Auth::id();
        $post->title = $request->input('title');
        $post->excerpt = $request->input('excerpt');
        $post->body = $request->input('body');
        $this->uploadImage($request, $post);
        $post->save();
        return redirect()
            ->route('post.index')
            ->with('success', 'Новый пост успешно создан');
    }
    /* ... */
}

Администратор

Первый пользователь, который зарегистрировался, будет обладать правами администратора — сможет редактировать и удалять не только свои посты, но и посты других пользователй. Мы бы могли добавить еще одно поле admin в таблицу базы данных users. Но тогда надо будет и создавать какую-то админку, а у нас блог с минимальным функционалом. Так что просто будем проверять, что идентификатор администратора равен единице.

class PostController extends Controller {
    /* ... */
    public function edit($id) {
        $post = Post::findOrFail($id);
        // проверяем права пользователя на это действие
        if (!$this->checkRights($post)) {
            return redirect()
                ->route('post.index')
                ->withErrors('У вас нет прав на это действие');
        }
        return view('posts.edit', compact('post'));
    }
    /* ... */
    public function update(PostRequest $request, $id) {
        $post = Post::findOrFail($id);
        // проверяем права пользователя на это действие
        if (!$this->checkRights($post)) {
            return redirect()
                ->route('post.index')
                ->withErrors('У вас нет прав на это действие');
        }
        $post->title = $request->input('title');
        $post->excerpt = $request->input('excerpt');
        $post->body = $request->input('body');
        // если надо удалить старое изображение
        if ($request->input('remove')) {
            $this->removeImage($post);
        }
        // если было загружено новое изображение
        $this->uploadImage($request, $post);
        // все готово, можно сохранять пост в БД
        $post->update();
        return redirect()
            ->route('post.show', compact('id'))
            ->with('success', 'Пост успешно отредактирован');
    }
    /**
     * Проверяет права пользователя на редактирование и удаление поста
     */
    private function checkRights(Post $post) {
        return Auth::id() == $post->author_id || Auth::id() == 1;
    }
    /* ... */
    public function destroy($id) {
        $post = Post::findOrFail($id);
        // проверяем права пользователя на это действие
        if (!$this->checkRights($post)) {
            return redirect()
                ->route('post.index')
                ->withErrors('У вас нет прав на это действие');
        }
        $this->removeImage($post);
        $post->delete();
        return redirect()
            ->route('post.index')
            ->with('success', 'Пост был успешно удален');
    }
}
@extends('layouts.site', ['title' => $post->title])

@section('content')
    <div class="row">
        <div class="col-12">
            <div class="card mt-4 mb-4">
                <div class="card-header">
                    <h1>{{ $post->title }}</h1>
                </div>
                <div class="card-body">
                    <img src="{{ $post->image ?? asset('img/default.jpg') }}" alt="" class="img-fluid">
                    <p class="mt-3 mb-0">{{ $post->body }}</p>
                </div>
                <div class="card-footer">
                    <div class="clearfix">
                        <span class="float-left">
                            Автор: {{ $post->author }}
                            <br>
                            Дата: {{ date_format($post->created_at, 'd.m.Y H:i') }}
                        </span>
                        <span class="float-right">
                        @auth
                            @if (auth()->id() == $post->author_id || auth()->id() == 1)
                            <a href="{{ route('post.edit', ['id' => $post->post_id]) }}"
                               class="btn btn-dark mr-2">Редактировать</a>
                            <!-- Форма для удаления поста -->
                            <form action="{{ route('post.destroy', ['id' => $post->post_id]) }}"
                                  method="post" onsubmit="return confirm('Удалить этот пост?')"
                                  class="d-inline">
                                @csrf
                                @method('DELETE')
                                <input type="submit" class="btn btn-danger" value="Удалить">
                            </form>
                            @endif
                        @endauth
                        </span>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Страница 404

Разместим картинку 404.jpg в директории public/img, где она будет доступна из веб. Создадим директорию resources/views/errors и внутри нее — шаблон 404.blade.php.

@extends('layouts.site', ['title' => 'Страница не найдена'])

@section('content')
    <div class="row">
        <div class="col-12">
            <div class="card mt-4 mb-4">
                <div class="card-header">
                    <h1>Страница не найдена</h1>
                </div>
                <div class="card-body">
                    <img src="{{ asset('img/404.jpg') }}" alt="" class="img-fluid">
                    <p class="mt-3 mb-0">Запрошенная страница не найдена.</p>
                </div>
            </div>
        </div>
    </div>
@endsection

Поиск: Laravel • Web-разработка • Авторизация • Аутентификация • Блог • Пользователь • Права доступа • Практика • Фреймворк • База данных • MySQL • PHP

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.