範例二:使用者認証

第一節

大藍圖

如果你是剛剛接觸CakePHP的新手,可能會想將這裡的程式碼直接複製到系統內使用。 警告你千萬別這麼做!本章主要是討論Cake的內部運作方式,而不是系統的安全性,所以有很多明顯的漏洞存在。 再強調一次,本章旨在說明Cake的內部運作方式,並幫你建立系統的藍圖。

Cake透過內建的ACL引擎做權限控制,但辨識使用者和持續追踪使用者又怎麼辦呢? 到目前為止,辨識使用者的方法在每個應用程式中都不大相同。 有些使用雜湊將密碼編碼,有些使用LDAP認識,幾乎大部分系統的User model都有那麼點不同。 在此,我們也將這部分留給你。會有任何不同嗎?我們也不知道。 但現在不值得花過多心思將此功能納入,因為要在Cake裡建立自己的使用者認証設定實在太容易了。

目前只需做三件事:

  1. 認証使用者(通常是比對使用者名稱和密碼)

  2. 持續追踪流覽中的使用者(通常是使用session)

  3. 檢查使用者是否已被認証通過(同樣透過與session的互動完成)

在此範例內,我們將建立一套簡單的使用者認証系統。 這支假想的程式是假設在辦公室裡追踪關於使用者的資料與相關的筆記。 系統所有的功能都會放在認証系統後面,除了一些沒有安全顧慮的網頁,像顯示存放於系統中使用者的姓名和職稱。

我們由示範如何檢驗企圖使用系統的使用者。 認証後的使用者資訊會透過Session component存放於PHP sesiion。 一旦我們從session取得使用者資訊,就會在程式中放入檢查的動作,確保使用者沒有進入不允許進入的地方。

有件事要注意-認証和權限控制是兩馬子事。 這個範裡所做的,只是要確定使用者就是他說的那位,然後提供基本的存取權。 如果想調証提供的存限,請參考"權限控制表"一章,有詳細的說明。 但現在,先專注於簡單的使用者認証即可。

我也應該這麼說,在範例中的程式並能提供足夠的安全性。 我們只是要給你足夠的概念,幫你建立系統藍圖而以。

第二節

認証與持續追踪

首先,我們需要一個方法儲存想要使用系統的使用者資料。 這套使用者管理系統將使用者資料存於資料庫中,資料表建立的SQL指令如下:

使用者管理系統的'users'資料表

CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(255) NOT NULL,
`password` varchar(32) NOT NULL,
`first_name` varchar(255) NOT NULL,
`last_name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
)

相當容易吧!這個資料表的Cake Model也非常單純:

<?php
class User extends AppModel
{
    var $name = 'User';
}
?>

再來需要的是登入的view和action。 view提供使用者輸入登入資料的介面,而action則負責檢查使用者是否被允許進入使用。 view只是一般的HTML表單,透過Cake的Html Helper幫忙建立:

/app/views/users/login.thtml

<?if ($error): ?>
<p>The login credentials you supplied could not be recognized. Please try again.</p>
<? endif; ?>

<form action="<?php echo $html->url('/users/login'); ?>" method="post">
<div>
    <label for="username">Username:</label>
    <?php echo $html->input('User/username', array('size' => 20)); ?>
</div>
<div>
    <label for="password">Password:</label>
    <?php echo $html->password('User/password', array('size' => 20)); ?>
</div>
<div>
    <?php echo $html->submit('Login'); ?>
</div>
</form>

這個view示範了一個簡單的登入系統,讓使用者輸入資料。 處理這個表單的action是/users/login,擁有此action的UsersController內容如下:

/app/controllers/users_controller.php (部分)

<?php
class UsersController extends AppController
{
    function login()
    {
        //如果沒有提交任何資料,就不用顯示錯誤訊息
        $this->set('error', false);

        // 如果使用者提交了某些資料:
        if (!empty($this->data))
        {
            // 首先,看看資料庫有沒有表單資料內所說的這位使用者存在:

            $someone = $this->User->findByUsername($this->data['User']['username']);

            // 這時,$someone應該放著使用者名稱,或是沒有資料。
            // 讓我們檢查一下表單資料內提供的密碼與存放在資料庫的中的是否一樣。

            if(!empty($someone['User']['password']) && $someone['User']['password'] == $this->data['User']['password'])
            {
                // 若存放在資料庫的密碼有經過編碼,比對的程式就看起來如下:
                // md5($this->data['User']['password']) == ...

                // 程式執行到這裡,就表示密碼已經確認相同了。接下來建立一些基本的
                // session資料將已登入的使用者記起來。

                $this->Session->write('User', $someone['User']);

                // 現在這個資料已經存在session裡了,可以將它傳到接下來流覽到
                // 的網頁裡

                $this->redirect('/clients');
            }
            // 如果資料錯誤:
            else
            {
                // 記得view裡的$error變數嗎?在此將它設成true:
                $this->set('error', true);
            }
        }
    }

    function logout()
    {
        // 如果使用者按下登出鈕,就將他們帶到這兒。
        // 我們要所的就是把session資料刪除:

        $this->Session->delete('User');

        // 然後將使用者帶到某處:
     
        $this->redirect('/');
    }
}
?>

不錯喔:login() action的內容可以少於20行。 這個action的結果只有二個,一個是使用者通過認識,資料被寫到session裡並轉交給其他的網頁, 另一個是使用者登入錯誤,被踢出系統,看到一堆錯誤訊息。

第三節

程式中的權限檢查

現在,我們已經可以為使用者進行行認証,再來就是將所有企圖由別的管道進入系統的爛人踢出去!

有個簡便的方法,就是在AppControler建立檢查session的函式,讓他幫你踢爛人。

/app/app_controller.php

<?php
class AppController extends Controller
{
    function checkSession()
    {
        // 如果session資料不存在...
        if (!$this->Session->check('User'))
        {
            // 強迫使用者登入
            $this->redirect('/users/login');
            exit();
        }
    }
}
?>

現在有了一個每個controller都可以使用的函式,確認使用者沒登入前無法使用任何動作。 程式完成後,就可以在任何地方檢查權限,這兒有個範例:

在執行所有action之前強迫認証

<?php
class NotesController extends AppController
{
    // 不讓任何未經認証的使用者看到任何action嗎?
    // 讓beforeFilter幫你呼叫checkSession,這函式會在所有action
    // 執行前被呼叫。

    function beforeFilter()
    {
        $this->checkSession();
    }
}
?>

在某個action執行前強迫認証

<?php
class NotesController extends AppController
{
    function publicNotes($clientID)
    {
        // 大家都可以使用這個action...
    }

    function edit($noteId)
    {
        // 只有經過認証的人可以使用這個action
        $this->checkSession();
    }
}
?>

好啦,基本的知識都有了,再來就是實作更進階的功能了。 整合Cake的ACL component是個不錯的下一步。


附錄:讀者筆記