라라벨은 기본적으로 인증기능을 지원하지만 상황에 따라 커스텀을 해야하는 경우가 있다.
목표
- 라라벨인증 기능은 사용자가 이용하고, 커스텀한 인증은 관리자가 이용할 수 있게 한다.
목차
- 프로젝트 생성
- 라라벨인증 기능 설치
- 모델 및 마이그레이션 생성
- 모델 migrate
- 가드, 프로바이더, 패스워드 지정
- 미들웨어 생성 및 등록
- 라우트 생성
- 컨트롤러, 뷰 생성
1. 프로젝트 생성
laravel new blog
cd ./blog
php artisan key:generate
composer install
2. 라라벨 인증기능 설치
라라벨인증을 설치하는 이유
- 사용자는 기본인증을 사용하기로 함
- 커스텀인증은 처음부터 구현하는것 보다 기본인증을 복사해서 사용하는것이 시간을 절약할 수 있다.
composer require laravel/ui --dev
npm install
npm run dev
3. 모델 및 마이그레이션 생성
기본인증에 사용되는 User모델이 있지만 관리자는 새로운 테이블로 관리하려고하기 때문에 새로운 모델과 마이그레이션을 생성해야 한다.
하지만 메일인증에 사용하는 password_resets 테이블은 분리하지 않고 공통으로 사용하기로 한다.
php artisan make:model Admin -m
‘-m’ 옵션을 사용하면 마이그레이션도 같이 생성된다.
Admin 모델 정의
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class Admin extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
}
마이그레이션 파일을 열어 테이블 스키마를 생성한다.
public function up()
{
Schema::create('admins', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('admins');
}
4. 모델 migrate
migrate 명령어를 통해 테이블을 생성해주자
php artisan migrate
5. 가드, 프로바이더, 패스워드 지정
config/auth.php 파일을 열어 생성한 Admin 모델에 대해 가드, 프로바이더, 패스워드를 추가해준다.
'guards' => [
...
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
'providers' => [
...
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
],
'passwords' => [
...
'admin' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
- guards : 매요청마다 어떻게 인증할 것인가
- providers : 저장소에서 사용자를 어떻게 찾을 것인가
- passwords : 사용자 테이블에 대한 암호를 재설정하는데 사용하는 브로커
6. 미들웨어 생성과 등록
라우트를 보호할 목적으로 미들웨어를 생성한다. 기본인증에 사용되는 미들웨어 클래스 Authenticate , RedirectIfAuthenticated 를 동일하게 구현할 예정이다.
미들웨어 생성
php artisan make:middleware AdminAuthenticate
php artisan make:middleware AdminRedirectIfAuthenticated
- AdminAuthenticate : 인증된 사용자가 아니면 로그인 페이지로 리다이렉트
- AdminRedirectIfAuthenticated : 인증되지 않은 요청만 허용, 로그인된 사용자라면 홈으로 리다이렉트
AdminAuthenticate 미들웨어는 상속받은Illuminate\Auth\Middleware\Authenticate 클래스의 redirectTo메서드를 오버라이드 해서 리다이렉트 경로를 지정해준다.
#AdminAuthenticate.php
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('admin.login');
}
}
AdminRedirectIfAuthenticated 는 클래스를 상속받기 않기 때문에 handle매서드를 지정해주면 된다. Auth파사드를 사용하기 위해 Illuminate\Support\Facades\Auth 네임스페이스 사용한다.
#AdminRedirectIfAuthenticated.php
use Illuminate\Support\Facades\Auth;
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/admin/home');
}
return $next($request);
}
정의한 미들웨어를 app/http/kernel.php 에 등록한다.
protected $routeMiddleware = [
...
'admin.auth' => \App\Http\Middleware\AdminAuthenticate::class,
'admin.guest' =>\App\Http\Middleware\AdminRedirectIfAuthenticated::class,
];
7. 라우트 생성
라라벨 인증에 사용되는 라우트는 아래와 같다. 아래의 기본인증 라우트는 그대로 두고 새로 만든 admin전용 라우트를 생성한다.
- 기본인증 라우트
Method | URI | Name | Action | Middleware |
GET|HEAD | login | login | App\Http\Controllers\Auth\LoginController@showLoginForm | web,guest |
POST | login | App\Http\Controllers\Auth\LoginController@login | web,guest | |
POST | logout | logout | App\Http\Controllers\Auth\LoginController@logout | web |
GET|HEAD | register | register | App\Http\Controllers\Auth\RegisterController@showRegistrationForm | web,guest |
POST | register | App\Http\Controllers\Auth\RegisterController@register | web,guest | |
GET|HEAD | password/confirm | password.confirm | App\Http\Controllers\Auth\ConfirmPasswordController@showConfirmForm | web,auth |
POST | password/conform | App\Http\Controllers\Auth\ConfirmPasswordController@confirm | web,auth | |
GET|HEAD | password/reset | password.request | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web |
POST | password/email | password.email | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail | web |
GET|HEAD | password/reset/{token} | password.reset | App\Http\Controllers\Auth\ResetPasswordController@showResetForm | web |
POST | password/reset | password.update | App\Http\Controllers\Auth\ResetPasswordController@reset | web |
GET|HEAD | email/verify | verification.notice | App\Http\Controllers\Auth\VerificationController@show | web,auth |
POST | email/resend | verification.resend | App\Http\Controllers\Auth\VerificationController@resend | web,auth,throttle:6,1 |
GET|HEAD | email/verify/{id}/{hash} | verification.verify | App\Http\Controllers\Auth\VerificationController@verify | web,auth,signed,throttle:6,1 |
GET|HEAD | home | home | App\Http\Controllers\HomeController@index | web,auth |
위 라우트는 web.php에 정의되어 있지 않고 Auth::routes(); 로 등록되어 있다. 기본인증 라우트를 확인 하려면 /vendor/laravel/ui/src/AuthRouteMethods.php를 확인하면 된다.
- admin 라우트
Method | URI | Name | Action | Middleware |
GET|HEAD | admin/login | admin.login | App\Http\Controllers\Admin\LoginController@showLoginForm | web,admin.guest |
POST | admin/login | App\Http\Controllers\Admin\ LoginController@login | web,admin.guest | |
POST | admin/logout | admin.logout | App\Http\Controllers\Admin\ LoginController@logout | web |
GET|HEAD | admin/register | admin.register | App\Http\Controllers\Admin\ RegisterController@showRegistrationForm | web,admin.guest |
POST | admin/register | App\Http\Controllers\Admin\ RegisterController@register | web,admin.guest | |
GET|HEAD | admin/password/confirm | admin.password.confirm | App\Http\Controllers\Admin\ ConfirmPasswordController@showConfirmForm | web,admin.auth |
POST | admin/password/conform | App\Http\Controllers\Admin\ ConfirmPasswordController@confirm | web,admin.auth | |
GET|HEAD | admin/password/reset | admin.password.request | App\Http\Controllers\Admin\ ForgotPasswordController@showLinkRequestForm | web |
POST | admin/password/email | admin.password.email | App\Http\Controllers\Admin\ ForgotPasswordController@sendResetLinkEmail | web |
GET|HEAD | admin/password/reset/{token} | admin.password.reset | App\Http\Controllers\Admin\ ResetPasswordController@showResetForm | web |
POST | admin/password/reset | admin.password.update | App\Http\Controllers\Admin\ResetPasswordController@reset | web |
GET|HEAD | admin/email/verify | admin.verification.notice | App\Http\Controllers\Admin\VerificationController@show | web,admin.auth |
POST | admin/email/resend | admin.verification.resend | App\Http\Controllers\Admin\VerificationController@resend | web,admin.auth,throttle:6,1 |
GET|HEAD | admin/email/verify/{id}/{hash} | admin.verification.verify | App\Http\Controllers\Admin\VerificationController@verify | web,admin.auth,signed,throttle:6,1 |
GET|HEAD | admin/home | admin.home | App\Http\Controllers\Admin\HomeController@index | web,admin.auth |
#routes/web.php
Route::namespace('Admin')->group(function(){
Route::prefix('admin')->group(function(){
//로그인
Route::get('login', 'LoginController@showLoginForm')->name('admin.login');
Route::post('login', 'LoginController@login');
Route::post('logout', 'LoginController@logout')->name('admin.logout');
//회원가입
Route::get('register', 'RegisterController@showRegistrationForm')->name('admin.register');
Route::post('register', 'RegisterController@register');
Route::get('home', 'HomeController@index')->name('admin.home');
//패스워드확인
Route::get('password/confirm', 'ConfirmPasswordController@showConfirmForm')->name('admin.password.confirm');
Route::post('password/confirm', 'ConfirmPasswordController@confirm');
//패스워드리셋
Route::get('password/reset', 'ForgotPasswordController@showLinkRequestForm')->name('admin.password.request');
Route::post('password/email', 'ForgotPasswordController@sendResetLinkEmail')->name('admin.password.email');
Route::get('password/reset/{token}', 'ResetPasswordController@showResetForm')->name('admin.password.reset');
Route::post('password/reset', 'ResetPasswordController@reset')->name('admin.password.update');
//이메일인증 확인
Route::get('email/verify', 'VerificationController@show')->name('admin.verification.notice');
Route::post('email/resend', 'VerificationController@resend')->name('admin.verification.resend');
Route::get('email/verify/{id}/{hash}', 'VerificationController@verify')->name('admin.verification.verify');
});
});
8. 컨트롤러 및 뷰생성
(1) 컨트롤러 뼈대 만들기
app/Http/Controller 아래에 Admin 폴더를 생성
app/Http/Controller/Auth 하위 파일을 Admin 폴더로 복사한다.
app/Http/Controller/HomeController를 Admin 폴더로 복사한다.
이제 모델 Admin에 대한 컨트롤러는 아래와 같은 디렉토리 구조를 같는다.
- app/Http/Controller/Admin/LoginController.php
- app/Http/Controller/Admin/RegisterController.php
- app/Http/Controller/Admin/ConfirmPasswordController.php
- app/Http/Controller/Admin/ForgotPasswordController.php
- app/Http/Controller/Admin/ResetPasswordController.php
- app/Http/Controller/Admin/VerificationController.php
- app/Http/Controller/Admin/HomeController.php
(2) 뷰 뼈대 만들기
resource/views 아래에 admin 폴더를 생성한다.
resource/views/auth 폴더를 복사해 resource/views/admin 아래에 붙여 넣는다.
resource/views/home.blade.php 복사해 resource/views/admin 아래에 붙여 넣는다.
(3) 구현하기
Admin 컨트롤러 각각의 파일을 열어 상단에 정의 되어있는 네임스페이스 정의를 변경해준다.
namespace App\Http\Controllers\Admin;
네임스페이스를 변경했으면 각 컨트롤러의 메소드 및 사용하고 있는 트레이트 메서드를 목적에 맞게 오버라이드 한다.
- 로그인 기능 구현
#Admin/LoginController.php
#Auth 파사드 추가
use Illuminate\Support\Facades\Auth;
protected $redirectTo = 'admin/home';
public function __construct()
{
#미들웨어 변경, admin 파라미터 전달
$this->middleware('admin.guest:admin')->except('logout');
}
#AuthenticatesUsers 트레이트 메서드 오버라이드
public function showLoginForm()
{
return view('admin.auth.login');
}
#가드지정
protected function guard()
{
return Auth::guard('admin');
}
#admin/login.blade.php
#라우트 변경
<form method="POST" action="{{ route('admin.login') }}">
- 회원가입
#Admin/RegisterController.php
#User 모델대신 Admin 모델을 사용
use App\Admin;
#Auth파사드 추가
use Illuminate\Support\Facades\Auth;
protected $redirectTo = '/admin/home';
public function __construct()
{
$this->middleware('admin.guest:admin');
}
public function showRegistrationForm()
{
return view('admin.auth.register');
}
protected function validator(array $data)
{
#unique 속성에 파라미터 admins(테이블명) 전달
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:admins'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
protected function create(array $data)
{
return Admin::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
protected function guard()
{
return Auth::guard('admin');
}
#admin/register.blade.php
#라우트 변경
<form method="POST" action="{{ route('admin.register') }}">
#Admin/HomeController.php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('admin.auth:admin');
}
public function index()
{
return view('admin.home');
}
}
- 비밀번호 확인
라라벨은 특정영역에 접근하기전에 비밀번호를 확인하도록 요청할 수 있는 기능이 있다.
#Admin/ConfirmPasswordController.php
protected $redirectTo = '/admin/home';
public function __construct()
{
#미들웨어에 파라미터를 전달해 가드를 지정
$this->middleware('admin.auth:admin');
}
public function showConfirmForm()
{
return view('admin.auth.passwords.confirm');
}
admin/auth/password/confirm.blade.php
<form method="POST" action="{{ route('admin.password.confirm') }}">
특정라우트를 보호하고 싶을때는 아래와 같이 미들웨어를 지정한다. admin.auth에는 admin 가드를 전달, password.cofirm에는 리다이렉트 라우트를 전달한다.
Route::get('test', function(){
return '이페이지가 보인다면 비밀번호 확인을 하신겁니다.';
})->middleware(['admin.auth:admin', 'password.confirm:admin.password.confirm']);
- 비밀번호 리셋
라라벨은 기본적으로 이메일로 인증주소를 받아 비밀번호를 초기화 시킨다. 이메일 설정이 되어있지 않으면 메일형식을 로그로 변경해주자.
# .env
MAIL_MAILER=log
비밀번호를 초기화하는 과정은 아래와 같다.
- admin/password/reset 페이지 접근
- 사용자 정보가 있으면 비밀번호 초기화페이지로 이동 없으면 로그인
- 이메일을 제출하면 해당 주소로 인증주소를 보낸다.
- 인증주소로 접속하여 비밀번호를 변경
이제 컨트롤러와 뷰를 수정하자
#admin/ForgotPasswordController.php
#Password 파사드 추가
use Illuminate\Support\Facades\Password;
public function __construct()
{
$this->middleware('admin.auth:admin');
}
public function showLinkRequestForm()
{
return view('admin.auth.passwords.email');
}
public function broker()
{
return Password::broker('admin');
}
#admin/auth/passwords/email.blade.php
#라우트 변경
<form method="POST" action="{{ route('admin.password.email') }}">
storage/logs/laravel.log 파일을 열어 인증주소를 확인해보면 localhost:8000/password/reset/token?email=your@email.com와 같은 형식으로 나타나는데 이 주소는 라라벨의 리셋주소이다. 따라서 Admin전용 리셋주소로 정보를 받아야 한다.
패스워드 재설정 이메일을 커스텀하려면 몇가지 작업이 필요하다.
- artisan 명령어를 이용해 notification 생성
- Admin모델에서 sendPasswordResetNotification 메서드 오버라이드
패스워드 재설정 참조링크
- https://laravel.kr/docs/6.x/passwords
- http://novate.co.uk/changing-the-laravel-5-3-password-reset-email-text/
php artisan make:notification AdminPasswordResetMailToken
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
class AdminPasswordResetMailToken extends ResetPasswordNotification
{
use Queueable;
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
}
if (static::$createUrlCallback) {
$url = call_user_func(static::$createUrlCallback, $notifiable, $this->token);
} else {
$url = url(config('app.url').route('admin.password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
}
return (new MailMessage)
->subject(Lang::get('Reset Password Notification'))
->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
->action(Lang::get('Reset Password'), $url)
->line(Lang::get('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
->line(Lang::get('If you did not request a password reset, no further action is required.'));
}
}
패스워드 재설정은 Illuminate\Auth\Notifications\ResetPassword의 클래스를 기본으로 사용하기 때문에 ResetPassword 을 상속받아 AdminPasswordResetMailToken를 작성해준다.
#Admin.php
use App\Notifications\AdminPasswordResetMailToken;
public function sendPasswordResetNotification($token)
{
$this->notify(new AdminPasswordResetMailToken($token));
}
모델이 사용하는 Illuminate\Foundation\Auth\User 의 클래스에 지정되어 있는 CanResetPassword 트레이트의 메서드 sendPasswordResetNotification를 오버라이드한다.
이제 이메일주소를 입력하고 인증주소를 확인해보면 원하는 주소로 변경되었음을 확인 할 수 있다.
인증메일부분을 처리했으니 인증주소를 통해 넘어온 사용자에게 비밀번호를 받고 저장하는 부분을 처리해주자.
#Admin/ResetPasswordController.php
#Request, Auth, Password 파사드 추가
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
protected $redirectTo = '/admin/home';
public function showResetForm(Request $request, $token = null)
{
return view('admin.auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
protected function guard()
{
return Auth::guard('admin');
}
public function broker()
{
return Password::broker('admin');
}
#라우트 변경
<form method="POST" action="{{ route('admin.password.update') }}">
- 이메일 인증하기
웹사이트를 사용하기전에 이메일을 인증해야하는 경우가 있다. 인증정보는 eamil_verified_at 필드에 저장된다.
기본인증기능에서는 라우트에 verify 옵션을 전달하면 사용할 수 있다.
Auth::routes(['verify' => true]);
커스텀한 인증에서는 비밀번호 초기화처럼 추가적으로 개발해야 한다.
#admin/VerificationController.php
use Illuminate\Http\Request;
protected $redirectTo = '/admin/home';
public function __construct()
{
$this->middleware('admin.auth:admin');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
public function show(Request $request)
{
return $request->user()->hasVerifiedEmail()
? redirect($this->redirectPath())
: view('admin.auth.verify');
}
#admin/auth/verify.blade.php
#라우트 변경
<form class="d-inline" method="POST" action="{{ route('admin.verification.resend') }}">
컨트롤러와 뷰쪽은 끝. 이제 인증메일을 발송하는 부분을 수정하자.
notification 클래스 생성
php artisan make:notification AdminVerifyEmail
Illuminate\Auth\Notifications\VerifyEmail 는 기본인증에 사용되는 클래스이다. Illuminate\Auth\Notifications\VerifyEmail 클래스를 상속받아 필요한 부분만 다시 정의해준다.
namespace App\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\URL;
use Illuminate\Auth\Notifications\VerifyEmail;
class AdminVerifyEmail extends VerifyEmail
{
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'admin.verification.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
}
생성한 AdminVerifyEmail 클래스를 Admin 모델에 적용해준다.
#Admin.php
use App\Notifications\AdminVerifyEmail;
public function sendEmailVerificationNotification()
{
$this->notify(new AdminVerifyEmail);
}