Laravel 學習筆記(19) - 登入驗證 (Authentication)

Authentication

Laravel 已經實作了登入機制,所以我們可以很快速的建立登入功能。驗證設定檔在 app/config/auth.php,而預設在 app/models 已經建立了一個 User.php 的 Model。在 auth.php 中的設定是 model 名稱為 User,table 名稱為 users,這些可以改成自己想要的。

!重要,在建立 users 資料表時,必須建立一個 remember_token 的字串欄位,長度 100、nullable。這個欄位是用來儲存 session 的 token。在 migrations 檔中可以使用
$table->rememberToken();
來建立。

儲存密碼

當使用者輸入密碼時,如果直接存入資料庫會有被偷盜的風險。Laravel 提供 Hash 類別,使用 Bcrypt hashing 的方式對密碼加密。

加密

$password = Hash::make('secret');
這樣原本的 'secret' 就會變成像這樣 $2y$10$ytWDg0ufsazdFkYsVnW.2eJhnc4gm5vZqHoEEycxqtCZCigWofGK6

驗證

if (Hash::check('secret', $password))
{
    //密碼正確
}

實戰使用者登入

在前面練習的"微型部落格"小專案中,我們將會加上一個登入頁面,讓登入的使用者才能進入頁面。

步驟:
  • 建立 migration 檔來新增 users 資料表
  • 使用 seeder 來新增使用者
  • 建立 Route
  • 建立 Controller
  • 建立登入表單頁面

建立 migration

開啟終端機,進入網站專案目錄,輸入 php artisan migrate:make create_users_table 會自動建立一個檔案 app/database/migrations/2015_01_08_060754_create_users_table.php ,開啟這個 php 檔,修改如下:
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function($table){
          $table->increments('id');
          $table->string('username');
          $table->string('email');
          $table->string('password');
          $table->string('remember_token', 100)->nullable();
          $table->timestamps();
      });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('users');
    }

}
記得一定要有 remeber_token 欄位。接下來再回到終端機中,輸入 php artisan migrate 如果沒出現錯誤,現在你在資料庫管理軟體中就可以看到 users 資料表了。

使用 seeder 來新增使用者

在 app/database/seeds 目錄中,新增一個檔案 UserTableSeeder.php:
<?php

class UserTableSeeder extends Seeder {

    public function run()
    {
        DB::table('users')->delete();

        User::create([
            'username' => 'Tony',
            'email'    => 'tony@mail.com',
            'password' => Hash::make('password'),
        ]);
    }
}
好了之後,開啟 app/database/seeds/DatabaseSeeder.php ,在 run() 方法中加入:
$this->call('UserTableSeeder');
完成。

回到終端機,輸入:php artisan db:seed 現在資料庫中 users 資料表已經建立一位使用者了。

建立 Route

打開 app/routes.php ,加入:
Route::get('login', 'LoginController@show');
Route::post('login', 'LoginController@login');
Route::get('logout', 'LoginController@logout');

建立 Controller

在 app/controllers 下建立 LoginController.php:
<?php

class LoginController extends BaseController {

    public function show()
    {

    }

    public function login()
    {

    }

    public function logout()
    {

    }

}
對應 route 要執行的動作,show() 用來顯示登入表單;login() 做驗證;logout() 登出。

建立登入表單頁面

1.登入頁
現在依序來加入功能。首先是 LoginController@show:
public function show()
{
    return View::make('admin.login');
}
在 app/views 新建一個目錄 admin,在其中新增一個檔案 login.blade.php ,結果是 app/views/admin/login.blade.php:
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Blog Login</title>
    <style type="text/css">
    .fail {width:200px; margin: 20px auto; color: red;}
    form {font-size:16px; color:#999; font-weight: bold;}
    form {width:160px; margin:20px auto; padding: 10px; border:1px dotted #ccc;}
    form input[type="text"], form input[type="password"] {margin: 2px 0 20px; color:#999;}
    form input[type="submit"] {width: 100%; height: 30px; color:#666; font-size:16px;}
    </style>
</head>
<body>
    @if ($errors->has('fail'))
        <div class="fail">{{ $errors->first('fail') }}</div>
    @endif
    {{ Form::open(['url'=>'login', 'method'=>'post']) }}
        {{ Form::label('email', 'Email') }}
        {{ Form::text('email') }}
        {{ Form::label('password', 'Password') }}
        {{ Form::password('password') }}
        {{ Form::submit('Login') }}
    {{ Form::close() }}
</body>
</html>
執行結果看起來像這樣:
2.驗證並登入
回到 LoginController@login 來處理使用者的登入驗證:
public function login()
{
    $input = Input::all();

    $rules = ['email'=>'required|email',
              'password'=>'required'
              ];

    $validator = Validator::make($input, $rules);

    if ($validator->passes()) {
        $attempt = Auth::attempt([
            'email' => $input['email'],
            'password' => $input['password']
        ]);

        if ($attempt) {
            return Redirect::intended('post');
        }

        return Redirect::to('login')
                ->withErrors(['fail'=>'Email or password is wrong!']);
    }

    //fails
    return Redirect::to('login')
                ->withErrors($validator)
                ->withInput(Input::except('password'));
}
我們將 email 及 password 兩個欄位設為必填(required)。使用 Auth 類別來做驗證。如果驗證成功,Auth::attempt() 方法會回傳 true,這時就可以把它導向內部頁面,這個範例是 post 頁面。之後若有取得登入者的資訊,可以:
Auth::user()->email;
這樣取得。
3.登出
回到 LoginController@logout:
public function logout()
{
    Auth::logout();
    return Redirect::to('login');
}
使用 Auth::logout() 將使用者登出,然後導回登入頁面。

開啟 app/views/site/home.blade.php 加入登出的連結:
@if(Auth::check())
    {{ Auth::user()->username}} 已登入,{{ HTML::link('logout', '登出') }}
@endif
Auth::check() 可以檢查使用者是否已經登入。

完成?還沒,我們還沒讓 post 頁面受到登入機制的管制,現在(記得先登出)你可以直接輸入網址: http://localhost/blog/public/post 你發現依然可以看到 post 頁面,頁面還沒受到限制。

如果每個頁面都去 Auth::check() 那就太麻煩了,我們直接從入口就截斷。開啟 app/routes.php,針對要管制的頁面加入:
Route::get('post', ['before'=>'auth', 'uses'=>'HomeController@index']);
'before'=>'auth',表示在進入這個 route 之前,要先執行 auth 這個過濾器(定義在 app/filters.php 中)。

如果要一個一個 route 去寫也太麻煩了,改用 group,並使用 before 關鍵字,在進入該 route 之前先執行 auth:
Route::group(['before'=>'auth'], function(){
    Route::get('post', 'HomeController@index');
    Route::get('post/create', 'HomeController@create');
    Route::post('post', 'HomeController@store');
    Route::get('post/{id}', 'HomeController@show');
    Route::get('post/{id}/edit', 'HomeController@edit');
    Route::put('post/{id}', 'HomeController@update');
    Route::delete('post/{id}', 'HomeController@destroy');
});
記得之前有提到,可以將同一位址往上提:
Route::group(['prefix'=>'post', 'before'=>'auth'], function(){
    Route::get('/', 'HomeController@index');
    Route::get('create', 'HomeController@create');
    Route::post('/', 'HomeController@store');
    Route::get('{id}', 'HomeController@show');
    Route::get('{id}/edit', 'HomeController@edit');
    Route::put('{id}', 'HomeController@update');
    Route::delete('{id}', 'HomeController@destroy');
});
之前還提到,如果你的條件符合,可以使用 resource:
Route::group(['before'=>'auth'], function(){
    Route::resource('post', 'HomeController');
});
完成,現在你已經無法直接輸入網址來進入頁面,請登入。
本文網址:http://blog.tonycube.com/2015/01/laravel-19-authentication.html
Tony Blog 撰寫,轉載時請註明出處及文章連結,謝謝 😀

6 則留言

  1. (ccckaass)

    hi

    抱歉 我在實做auth時遇到一點問題 想請您幫忙看看

    我的
    table : admin
    model目錄內 : Admin
    要驗證的欄位 : username及userpass
    auth.php 內: 'model' => 'Admin';
    'table' => 'admin',
    -------------------------------------------------------

    $attempt = Auth::attempt([
    'username' => $input['username'],
    'userpass' =>$input['userpass'],
    ]);

    執行後會無法驗證,並直接提示undefined index: password

    請問您有遇過類似情況嗎?
    =========================================================
    (抱歉,留言系統有問題,從新貼上你的留言)
    參考這篇 http://stackoverflow.com/questions/26073309/how-to-change-custom-password-field-name-for-laravel-4-and-laravel-5-user-auth

    因為 password 這個屬性是以 hardcode 寫死在 Laravel 裡,
    所以當你使用 'userpass' 這個屬性名稱時,Eloquent ORM 就會找不到這個欄位。
    除非你在資料庫中建立 userpass 這個欄位。而且同時還要去覆寫源始碼中的方法
    public function getAuthPassword()
    {
    //return $this->password;
    //改成
    return $this->userpass;
    }
    但基本上除了麻煩,日後也不易維議,
    建議是使用 password 方法最好。
    你的表單中依然可以用 $input['userpass'],反正最後是傳值給 password 屬性即可。

    回覆刪除
  2. 非常感謝您的回覆,問題已解決。

    回覆刪除
  3. remember_token 看官方文件似乎是用在實作 「記住我」這項功能, table 裡頭似乎沒有這個欄位也沒關係的, 因為laravel 用 laravel_session這個COOKIE來存session id的,如果有記住我的選項的話, 就會再開一個remember_webxxx的COOKIE來存放, 不曉得是不是這樣呢?

    回覆刪除
  4. 請教老師, 若是認證的資料是在第三方, 對方開api 出來認證通過會回傳token(類似jwt那種方法) , 這樣的方式, 有沒有實際的code 範例可以參考的?感恩~

    回覆刪除
    回覆
    1. 官方有支援,看看文件研究一下
      https://laravel.com/docs/5.4/authentication#adding-custom-guards

      刪除

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。