Laravelの既存AuthからSentinelに移動する方法
チュートリアルの通りにblogプロジェクト作成: Laravel Installation Authモジュール導入 php artisan make:auth Sentinelを導入してLaravelに統合する Sentinel Installation Sentinelへ異動するとしたらAuthに導入した時に作成されたClassを削除せずに継承したくて一部書き直しました。 Login/Logoutロジック <?php namespace AppHttpControllersAuth; use ...
- チュートリアルの通りにblogプロジェクト作成:
- Laravel Installation
- Authモジュール導入
php artisan make:auth
- Sentinelを導入してLaravelに統合する
- Sentinel Installation
Sentinelへ異動するとしたらAuthに導入した時に作成されたClassを削除せずに継承したくて一部書き直しました。
Login/Logoutロジック
<?php namespace AppHttpControllersAuth; use AppHttpControllersController; use IlluminateFoundationAuthAuthenticatesUsers; use IlluminateHttpRequest; use Sentinel; class LoginController extends Controller { /* |-------------------------------------------------------------------------- | Login Controller |-------------------------------------------------------------------------- | | This controller handles authenticating users for the application and | redirecting them to your home screen. The controller uses a trait | to conveniently provide its functionality to your applications. | */ use AuthenticatesUsers; /** * Where to redirect users after login. * * @var string */ protected $redirectTo = '/home'; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest', ['except' => 'logout']); } public function attemptLogin(Request $request) { $credentials = [ 'email' => $request->input('email'), 'password' => $request->input('password'), ]; return Sentinel::authenticate($credentials, $request->has('remember')); } public function authenticated(Request $request, $user) { $credentials = [ 'email' => $request->input('email'), 'password' => $request->input('password'), ]; return Sentinel::validateCredentials($user, $credentials); } protected function sendLoginResponse(Request $request) { $request->session()->regenerate(); $this->clearLoginAttempts($request); return $this->authenticated($request, Sentinel::getUser())? redirect()->intended($this->redirectPath()):redirect('/home'); } public function logout(Request $request) { Sentinel::logout(); $request->session()->flush(); $request->session()->regenerate(); return redirect('/'); } }
パスワード忘れ
<?php namespace AppHttpControllersAuth; use AppHttpControllersController; use IlluminateFoundationAuthSendsPasswordResetEmails; use IlluminateSupportFacadesPassword; use IlluminateHttpRequest; use Sentinel,Reminder; class ForgotPasswordController extends Controller { /* |-------------------------------------------------------------------------- | Password Reset Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling password reset emails and | includes a trait which assists in sending these notifications from | your application to your users. Feel free to explore this trait. | */ use SendsPasswordResetEmails; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest'); } public function sendResetLink(array $credentials) { Reminder::removeExpired(); // First we will check to see if we found a user at the given credentials and // if we did not we will redirect back to this current URI with a piece of // "flash" data in the session to indicate to the developers the errors. $user = Sentinel::findByCredentials($credentials); if (is_null($user)) { return Password::INVALID_USER; } $code = ""; $exists = Reminder::exists($user); if ($exists) { $code = $exists->code; } else { $reminder = Reminder::create($user); $code = $reminder->code; } // Once we have the reset token, we are ready to send the message out to this // user with a link to reset their password. We will then redirect back to // the current URI having nothing set in the session to indicate errors. $user->sendPasswordResetNotification($code); return Password::RESET_LINK_SENT; } public function sendResetLinkEmail(Request $request) { $this->validate($request, ['email' => 'required|email']); // We will send the password reset link to this user. Once we have attempted // to send the link, we will examine the response then see the message we // need to show to the user. Finally, we'll send out a proper response. $response = $this->sendResetLink( $request->only('email') ); return $response == Password::RESET_LINK_SENT ? $this->sendResetLinkResponse($response) : $this->sendResetLinkFailedResponse($request, $response); } }
パスワードリセット
<?php namespace AppHttpControllersAuth; use AppHttpControllersController; use IlluminateFoundationAuthResetsPasswords; use IlluminateContractsAuthCanResetPassword as CanResetPasswordContract; use IlluminateSupportFacadesPassword; use IlluminateHttpRequest; use Closure; use Sentinel,Reminder; use Log; class ResetPasswordController extends Controller { /* |-------------------------------------------------------------------------- | Password Reset Controller |-------------------------------------------------------------------------- | | This controller is responsible for handling password reset requests | and uses a simple trait to include this behavior. You're free to | explore this trait and override any methods you wish to tweak. | */ use ResetsPasswords; /** * Where to redirect users after resetting their password. * * @var string */ protected $redirectTo = '/home'; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest'); } public function reset(Request $request) { $this->validate($request, $this->rules(), $this->validationErrorMessages()); // Here we will attempt to reset the user's password. If it is successful we // will update the password on an actual user model and persist it to the // database. Otherwise we will parse the error and return the response. $response = $this->doReset( $this->credentials($request), function ($user, $token, $password) { Log::debug($user); Log::debug($token); Log::debug($password); $this->resetPassword($user, $token, $password); } ); // If the password was successfully reset, we will redirect the user back to // the application's home authenticated view. If there is an error we can // redirect them back to where they came from with their error message. return $response == Password::PASSWORD_RESET? $this->sendResetResponse($response) : $this->sendResetFailedResponse($request, $response); } private function doReset(array $credentials, Closure $callback) { // If the responses from the validate method is not a user instance, we will // assume that it is a redirect and simply return it from this method and // the user is properly redirected having an error message on the post. $user = $this->validateReset($credentials); if (! $user instanceof CanResetPasswordContract) { return $user; } $password = $credentials['password']; $token = $credentials['token']; // Once we have called this callback, we will remove this token row from the // table and return the response from this callback so the user gets sent // to the destination given by the developers from the callback return. $callback($user, $password, $token); return Password::PASSWORD_RESET; } protected function validateReset(array $credentials) { Reminder::removeExpired(); $user = Sentinel::findByCredentials($credentials); if (is_null($user)) { return Password::INVALID_USER; } if (! $this->validateNewPassword($credentials)) { return Password::INVALID_PASSWORD; } $reminder = Reminder::exists($user); if (($reminder==false) || (!is_null($reminder) && ($reminder->code!= $credentials['token']))) { return Password::INVALID_TOKEN; } return $user; } protected function resetPassword($user, $token, $password) { $reminder = Reminder::complete($user, $token, $password); Sentinel::login($user); } public function validateNewPassword(array $credentials) { if (isset($this->passwordValidator)) { list($password, $confirm) = [ $credentials['password'], $credentials['password_confirmation'], ]; return call_user_func( $this->passwordValidator, $credentials) && $password === $confirm; } return $this->validatePasswordWithDefaults($credentials); } /** * Determine if the passwords are valid for the request. * * @param array $credentials * @return bool */ protected function validatePasswordWithDefaults(array $credentials) { list($password, $confirm) = [ $credentials['password'], $credentials['password_confirmation'], ]; return $password === $confirm && mb_strlen($password) >= 6; } }
ユーザー登録
<?php namespace AppHttpControllersAuth; use AppUser; use AppHttpControllersController; use IlluminateSupportFacadesValidator; use IlluminateFoundationAuthRegistersUsers; use IlluminateHttpRequest; use Sentinel; class RegisterController extends Controller { /* |-------------------------------------------------------------------------- | Register Controller |-------------------------------------------------------------------------- | | This controller handles the registration of new users as well as their | validation and creation. By default this controller uses a trait to | provide this functionality without requiring any additional code. | */ use RegistersUsers; /** * Where to redirect users after registration. * * @var string */ protected $redirectTo = '/home'; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest'); } /** * Get a validator for an incoming registration request. * * @param array $data * @return IlluminateContractsValidationValidator */ protected function validator(array $data) { return Validator::make($data, [ 'name' => 'required|max:255', 'email' => 'required|email|max:255|unique:users', 'password' => 'required|min:6|confirmed', ]); } public function register(Request $request) { $validator = $this->validator($request->all()); if ($validator->fails()) { $this->throwValidationException( $request, $validator ); } Sentinel::authenticateAndRemember($this->create($request->all())); return redirect($this->redirectPath()); } /** * Create a new user instance after a valid registration. * * @param array $data * @return User */ protected function create(array $data) { $credentials = [ 'email' => $data['email'], 'password' => $data['password'], ]; return Sentinel::registerAndActivate($credentials); } }
ユーザーモデル
SentinelはSentinelなりにユーザーモデル持っていますので、Authのユーザーモデルを全面書き直してSentinelに使われているモデルを継承するのは必要となります。
<?php namespace App; use IlluminateNotificationsNotifiable; use CartalystSentinelUsersEloquentUser; use IlluminateAuthAuthenticatable; use IlluminateAuthPasswordsCanResetPassword; use IlluminateFoundationAuthAccessAuthorizable; use IlluminateContractsAuthAuthenticatable as AuthenticatableContract; use IlluminateContractsAuthAccessAuthorizable as AuthorizableContract; use IlluminateContractsAuthCanResetPassword as CanResetPasswordContract; class User extends EloquentUser implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { use Notifiable; use Authenticatable, Authorizable, CanResetPassword; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; }
Sentinel設定ファイル
Modelの変更にしたがってSentinelの設定ファイルの該当設定項目を変更しなければなりません
/* |-------------------------------------------------------------------------- | Users |-------------------------------------------------------------------------- | | Please provide the user model used in Sentinel. | */ 'users' => [ // 'model' => 'CartalystSentinelUsersEloquentUser', 'model' => 'AppUser', ],
その他
Middleware
以下のコメントで確認してみたらAuthが登録したルートはauth/guestという二つのmiddlewareに守られています。
$ php artisan route:list
Sentinelへ異動しましたのでauthに提供しているmiddlewareをそのまま使うわかがないでしょう。そういう訳で新しいmiddlwareを作成することにしました。
$php artisan make:middleware SentinelAuth
そしてSentinelAuthのロジックを以下のように追加する(※説明:認証されていないユーザからのアクセスは全てをloginページへリダイレクトさせること)
<?php namespace AppHttpMiddleware; use Closure; use Sentinel as Auth; class SentinelAuth { public function handle($request, Closure $next, ...$guards) { if(!Auth::check()) return redirect('/login'); return $next($request); } }
Kernelで登録したmiddlewareを切り替える
protected $routeMiddleware = [ //'auth' => IlluminateAuthMiddlewareAuthenticate::class, // Comment out 'auth' => AppHttpMiddlewareSentinelAuth::class, //追加 'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class, 'bindings' => IlluminateRoutingMiddlewareSubstituteBindings::class, 'can' => IlluminateAuthMiddlewareAuthorize::class, 'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class, 'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class, ];
見た内容の通り「guest」というmiddlewareの実施クラスはRedirectIfAuthenticatedで当ファイルを見て見たら少しい修正しないと行けないところがあります
<?php namespace AppHttpMiddleware; use Closure; //use IlluminateSupportFacadesAuth; //Comment out use Sentinel as Auth; //追加 class RedirectIfAuthenticated { /** * Handle an incoming request. * * @param IlluminateHttpRequest $request * @param Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { if(Auth::check()){ return redirect('/home'); } return $next($request); } }
Viewの修正
以下の2つViewの修正は必要になります。
@if (Route::has('login')) <div class="top-right links"> @if (Sentinel::check()) <a href="{{ url('/home') }}">Home</a> @else <a href="{{ url('/login') }}">Login</a> <a href="{{ url('/register') }}">Register</a> @endif </div> @endif
@if (Sentinel::guest()) <li><a href="{{ url('/login') }}">Login</a></li> <li><a href="{{ url('/register') }}">Register</a></li> @else
修正したところは結構多いですが自ら作るのに比べてコード量はある程度削減できたと思いましてそれにコードをなるべく多くリユースしたのでテスト作業も減られたはずです。