登入驗證

設定

很多時候我們都需要網站的登入功能,因為實在太常用了,所以 Laravel 直接就實作了整個登入機制,讓我們可以很快速的建立登入功能。

設定檔

app/config/auth.php

在這個檔案中,預設了一個可以儲存使用者的 Model 及資料表的名稱,

'model' => 'User',
'table' => 'users',

你可以在 app/models 中找到 User.php 這個 Model。而資料表的名稱就叫 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 檔來新增 users 資料表

開啟終端機,進入網站根目錄,輸入:

php artisan migrate:make create_users_table

會建立一個檔案 app/database/migrations/2015_01_08_060754_create_users_table.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');
//記得要加 remember_token 這個欄位,可手動指定
//$table->string('remember_token', 100)->nullable();
//或使用內建方法
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('users');
}
}

接下來到終端機中,輸入:

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' => '[email protected]',
'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', '[email protected]');
Route::post('login', '[email protected]');
Route::get('logout', '[email protected]');

我們會在 [email protected] 方法中回傳一個登入頁面;在 @login 方法中做登入驗證;在 @logout 方法中登出使用者。

建立 Controller

在 app/controllers 下建立 LoginController.php:

<?php
class LoginController extends BaseController {
public function show()
{
}
public function login()
{
}
public function logout()
{
}
}

對應到 Route 中所指定的方法。

1.登入頁

現在依序來加入功能。首先是 [email protected]

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.驗證並登入

接著編輯 [email protected],處理使用者的登入驗證:

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!']);
}
//輸入的資料不符合
return Redirect::to('login')
->withErrors($validator)
->withInput(Input::except('password'));
}

我們將 email 及 password 兩個欄位設為必填(required),同時使用 email 這個規則去驗證郵件格式是否正確。

輸入的資料都符合規定後,使用 Auth 類別來做驗證。如果驗證成功,Auth::attempt() 方法會回傳 true,這時就可以把它導向內部頁面,這個範例是 post 頁面。假如驗證失敗,就導回登入頁面,同時回傳錯誤訊息。

如果輸入的資料就已經不符合規定,那也不必驗證啦,直接返回登入頁面。不過,在 ->withInput 的部份,我們用 Input::except('password') 來排除密碼欄位內容的回傳,因為不需要。

在成功驗證並登入之後,如果要取得登入者的資訊,可以使用 Auth::user() 來取得,如下:

Auth::user()->email;

3.登出

最後來處理登出的功能,編輯 [email protected]

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() 可以檢查使用者是否已經登入。

4.頁面的登入檢查

完成了嗎?還沒,我們還沒讓 post 頁面受到登入機制的管制,現在(在登出的狀態)你可以直接輸入網址:

http://localhost/blog/public/post

你發現即使我們尚未登入,依然可以看到 post 頁面。

通常會每個必須被管制的頁面都要加檢查機制,但每個頁面都去 Auth::check() 不僅沒效率,也太麻煩了,我們直接從入口 (Route) 就截斷。編輯 app/routes.php,針對要管制的頁面 post 加入:

Route::get('post', ['before'=>'auth', 'uses'=>'[email protected]']);

'before'=>'auth',表示在進入這個 route 之前,要先執行 auth 這個過濾器(定義在 app/filters.php)。

如果要一個一個 route 去寫也太麻煩了,改用 group:

Route::group(['before'=>'auth'], function(){
Route::get('post', '[email protected]');
Route::get('post/create', '[email protected]');
Route::post('post', '[email protected]');
Route::get('post/{id}', '[email protected]');
Route::get('post/{id}/edit', '[email protected]');
Route::put('post/{id}', '[email protected]');
Route::delete('post/{id}', '[email protected]');
});

這樣在這個 group 之下的所有頁面都會受到 auth 過濾器的檢查。

在 [Route 進階] 那個章節,有提到可以將同一位址往上提,於是可以修改成:

Route::group(['prefix'=>'post', 'before'=>'auth'], function(){
Route::get('/', '[email protected]');
Route::get('create', '[email protected]');
Route::post('/', '[email protected]');
Route::get('{id}', '[email protected]');
Route::get('{id}/edit', '[email protected]');
Route::put('{id}', '[email protected]');
Route::delete('{id}', '[email protected]');
});

當然,如果你的條件符合,也可以使用 resource 的方式:

Route::group(['before'=>'auth'], function(){
Route::resource('post', 'HomeController');
});

完成,現在你已經無法直接輸入網址來進入頁面,請登入。