The Moment

阿宅的筆記

Manually Authenticating Users

做網站總免不了要處理使用者登入、註冊、認証授權…的工作, Laravel 這個 PHP 框架很好心的內建了一整套給你,只要呼叫 php artisan make:auth 再給它適當的資料庫權限執行他的 mirgration sql ,幾乎就可以無痛地擁有一套使用者管理系統,以下是一點使用及客製化的筆記

  • 在資料庫建立使用者的資料表,如果使用 database/migrations 下 create user table 的程式讓 Laravel 幫你建的話,名稱預設會叫 Users (這跟 Laravel 使用 Eloquent Model 的方式有關),不想叫這個名字也沒關係,之後可以改掉。至於資料表中的欄位,Laravel 的要求只有兩個,一個是 password 的欄位長度至少要 60 個字視,而若是要啟用 remember me 功能,要有個至少100 字元長度的 remember_token 的欄位。

  • 在專案的 app/Models 下建立 Eloquent Model Users ( Models 目錄要自行建立),Laravel 預設會使用 default 的 db connection 連到 Users 這個資料表,並以 id 當 primary key 查詢,若是這三個條件都不想用預設值,那也只要指定好 Users 中 $table / $primaryKey / $connection 三個屬性即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <?php
    namespace App;
    use Illuminate\Notifications\Notifiable;
    use Illuminate\Foundation\Auth\User as Authenticatable;
    class User extends Authenticatable
    {
    use Notifiable;
    protected $table = 'User';
    /** primary key of the table **/
    public $primaryKey = 'my_primary_key';
    /** database connection name in database.php **/
    protected $connection = 'my_connection';
    /**
    * The attributes that are mass assignable.
    *
    * @var array
    */
    protected $fillable = [
    'name', 'email', 'password', 'username'
    ];
    /**
    * The attributes that should be hidden for arrays.
    *
    * @var array
    */
    protected $hidden = [
    'password', 'remember_token',
    ];
    }
  • 編輯 config/auth.php 修改認証設定,剛開始沒有太搞怪的話,應該只要修改 User Providers 區塊,把 users 這個 model 指到對的 class 去就好

    1
    2
    3
    4
    5
    6
    7
    8
    ...skip
    'providers' => [
    'users' => [
    'driver' => 'eloquent',
    'model' => App\Models\Users::class,
    ],
    ],
    ...skip
  • 設定相關路由。如果一開頭使用 php artisan make:auth 的話,它會在路由設定檔中 routes/web.app ( 5.4 之前的版本好像是在專案根目錄下的 routes.php) 加上一行 Auth::routes() ,執行的 php artisan route:list 會發現多出一大串的路由設定如下,除了 home 用 auth 外,其他的自動套用 guest 這個 middleware

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    | GET|HEAD | home | | App\Http\Controllers\HomeController@index | web,auth |
    | POST | login | | App\Http\Controllers\Auth\LoginController@login | web,guest |
    | GET|HEAD | login | login | App\Http\Controllers\Auth\LoginController@showLoginForm | web,guest |
    | POST | logout | logout | App\Http\Controllers\Auth\LoginController@logout | web |
    | POST | password/email | password.email | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail | web,guest |
    | GET|HEAD | password/reset | password.request | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web,guest |
    | POST | password/reset | | App\Http\Controllers\Auth\ResetPasswordController@reset | web,guest |
    | GET|HEAD | password/reset/{token} | password.reset | App\Http\Controllers\Auth\ResetPasswordController@showResetForm | web,guest |
    | GET|HEAD | register | register | App\Http\Controllers\Auth\RegisterController@showRegistrationForm | web,guest |
    | POST | register | | App\Http\Controllers\Auth\RegisterController@register | web,guest |
  • 上面這一串完整的 code 在 https://github.com/laravel/framework/blob/5.4/src/Illuminate/Routing/Router.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public function auth()
    {
    // Authentication Routes...
    $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
    $this->post('login', 'Auth\LoginController@login');
    $this->post('logout', 'Auth\LoginController@logout')->name('logout');
    // Registration Routes...
    $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
    $this->post('register', 'Auth\RegisterController@register');
    // Password Reset Routes...
    $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
    $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
    $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
    $this->post('password/reset', 'Auth\ResetPasswordController@reset');
    }
  • 由於這個專案僅需要使用者登入認証的部份,所以手動在 routes/web.php 中加入包含登入認証、登入的網頁、登出、及登入後顯示的頁面共四個路由,避免不必要的路由變成沒人管理、被入侵的點

    1
    2
    3
    4
    5
    6
    ...skip
    Route::post('/login', 'App\Http\Controllers\Auth\myController@login');
    Route::get('/login', 'App\Http\Controllers\Auth\myController@showLoginForm')->name('login');
    Route::post('/logout', 'App\Http\Controllers\Auth\myController@logout')->name('logout');
    Route::get('/home', 'HomeController@index');
    ...skip
  • 有了路由後,接著要來寫處理路由的 Controller 了 (app/Http/Controllers/myController.php)。Laravel 生的代碼如下,其中 $redirectTo 指定在完成登入後要導到那去. 另外,預設是使用 email 欄位當帳號,如果要用其他欄位當帳話的話,要自行加一個 username () 函式回傳要當帳號的植位名稱,這邊是用 username 當範例。至於在路由中寫的 login / showLoginForm / logout 三個函式並沒有明確地寫在這邊。相關的函式 Laravel 寫在 AuthenticateUsers 這個 trait 中,透過 use 注入到 class 中,如果有客製的需求,自行覆寫掉即可 ( username() 即是被覆寫掉的函式)。另外,Laravel 的範例中,會在建構子中加入路由的 middleware ,先前看到的 guest 就是在這個時候加進來的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?php
    namespace App\Http\Controllers;
    use App\Http\Controllers\Controller;
    use Illuminate\Foundation\Auth\AuthenticatesUsers;
    class myController extends Controller
    {
    use AuthenticatesUsers;
    protected $redirectTo = '/home';
    public function __construct()
    {
    $this->middleware('guest', ['except' => 'logout']);
    }
    public function username(){
    return 'username';
    }
    }
  • 編寫登入表單的網頁,由於我們等下要使用內建的登入路由,它會去叫用 resources/views/auth/login.blade.php,所以只能乖乖的生一個給它,如果不想用這個預設的路徑,可以在login controller 中覆寫 showLoginForm() 直接回使你要使用的 view 即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <form class="form-horizontal" role="form" method="POST" action="{{ route('login') }}">
    {{ csrf_field() }}
    <div class="form-group{{ $errors->has('account') ? ' has-error' : '' }}">
    <label for="account" class="col-md-4 control-label">Account</label>
    <div class="col-md-6">
    <input id="account" type="text" class="form-control" name="account" value="{{ old('account') }}" autofocus>
    @if ($errors->has('account'))
    <span class="help-block">
    <strong>{{ $errors->first('account') }}</strong>
    </span>
    @endif
    </div>
    </div>
    <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
    <label for="password" class="col-md-4 control-label">Password</label>
    <div class="col-md-6">
    <input id="password" type="password" class="form-control" name="password" required>
    @if ($errors->has('password'))
    <span class="help-block">
    <strong>{{ $errors->first('password') }}</strong>
    </span>
    @endif
    </div>
    </div>
    <div class="form-group">
    <div class="col-md-6 col-md-offset-4">
    <div class="checkbox">
    <label>
    <input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> Remember Me
    </label>
    </div>
    </div>
    </div>
    <div class="form-group">
    <div class="col-md-8 col-md-offset-4">
    <button type="submit" class="btn btn-primary">
    Login
    </button>
    </div>
    </div>
    </form>
  • 補一個登入後顯示頁面的 controller app/Http/Controller/HomeController.php及網頁 resources/views/home.blade.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
    namespace App\Http\Controllers;
    use Illuminate\Http\Request;
    class HomeController extends Controller
    {
    public function index()
    {
    return view('home');
    }
    }
1
2
3
4
5
<html>
<body>
hellow world!
</body>
</html>
  • 這幾個登入認証相關的路由都會經過的 guest middleware 是定義在 app/Kernel.php 中,指到 app/Http/Middleware/RedirectIfAuthenticated.php,Laravel 預設的程式碼很簡單,大概就是要是已經登入了的話,那就跳到 /home 去,要是沒有,就跳到原先指定的網址,這邊的程式碼是寫死的,要是登入後的首頁不叫 /home ,要記得改一下。

    1
    2
    3
    4
    5
    6
    7
    8
    public function handle($request, Closure $next, $guard = null)
    {
    if (Auth::guard($guard)->check()) {
    return redirect('/home');
    }
    return $next($request);
    }
  • 如果以上寫的沒什麼問題,打開網頁 /login , 因為沒登入過,所以 guest 中的登入判定會失敗,會轉到指定的 /login 看到登入的表單。送出登入後,會 post 給 myController 的 login 函式處理,若是成功就跳到 $redirectTo 指定的網址(/home),要是失敗就送回失敗訊息。之後若是再打開 /login , 因為登入判定成功,會直接轉到 /home 去,而後其他頁面若是要判定是否登入,只要用 Auth::check() 即可。由於登入檢查幾乎是制式的語法,可以仿照 guest 寫一個 auth 的 middleware 套在需要檢查的路由前自動執行。收工。

  • p.s 若是要取用登入者的資訊,可以使用 $user = Auth::user() 的方式,或是透過 eloquent 威能,用 Auth::id() 取得屬性值。