Laravel核心解讀 -- 擴展用戶認證系統

<h1>擴展用戶認證系統</h1> <p>上一節咱們介紹了Laravel Auth系統實現的一些細節知道了Laravel是如何應用看守器和用戶提供器來進行用戶認證的,可是針對咱們本身開發的項目或多或少地咱們都會須要在自帶的看守器和用戶提供器基礎之上作一些定製化來適應項目,本節我會列舉一個在作項目時遇到的具體案例,在這個案例中用自定義的看守器和用戶提供器來擴展了Laravel的用戶認證系統讓它能更適用於咱們本身開發的項目。</p> <p>在介紹用戶認證系統基礎的時候提到過Laravel自帶的註冊和登陸驗證用戶密碼時都是去驗證採用<code>bcypt</code>加密存儲的密碼,可是不少已經存在的老系統中用戶密碼都是用鹽值加明文密碼作哈希後存儲的,若是想要在這種老系統中應用Laravel開發項目的話那麼咱們就不可以再使用Laravel自帶的登陸和註冊方法了,下面咱們就經過實例看看應該如何擴展Laravel的用戶認證系統讓它可以知足咱們項目的認證需求。</p> <h3>修改用戶註冊</h3> <p>首先咱們將用戶註冊時,用戶密碼的加密存儲的方式由<code>bcypt</code>加密後存儲改成由鹽值與明文密碼作哈希後再存儲的方式。這個很是簡單,上一節已經說過Laravel自帶的用戶註冊方法是怎麼實現了,這裏咱們直接將<code>\App\Http\Controllers\Auth\RegisterController</code>中的<code>create</code>方法修改成以下:</p>php

/**
 * Create a new user instance after a valid registration.
 *
 * @param  array  $data
 * @return User
 */
protected function create(array $data)
{
    $salt = Str::random(6);
    return User::create([
        'email' =&gt; $data['email'],
        'password' =&gt; sha1($salt . $data['password']),
        'register_time' =&gt; time(),
        'register_ip' =&gt; ip2long(request()-&gt;ip()),
        'salt' =&gt; $salt
    ]);
}

<p>上面改完後註冊用戶後就能按照咱們指定的方式來存儲用戶數據了,還有其餘一些須要的與用戶信息相關的字段也須要存儲到用戶表中去這裏就再也不贅述了。</p> <h3>修改用戶登陸</h3> <p>上節分析Laravel默認登陸的實現細節時有說登陸認證的邏輯是經過<code>SessionGuard</code>的<code>attempt</code>方法來實現的,在<code>attempt</code>方法中<code>SessionGuard</code>經過<code>EloquentUserProvider</code>的<code>retriveBycredentials</code>方法從用戶表中查詢出用戶數據,經過 <code>validateCredentials</code>方法來驗證給定的用戶認證數據與從用戶表中查詢出來的用戶數據是否吻合。</p> <p>下面直接給出以前講這塊時引用的代碼塊:</p>git

class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
    public function attempt(array $credentials = [], $remember = false)
    {
        $this-&gt;fireAttemptEvent($credentials, $remember);

        $this-&gt;lastAttempted = $user = $this-&gt;provider-&gt;retrieveByCredentials($credentials);
       //若是登陸認證經過,經過login方法將用戶對象裝載到應用裏去
        if ($this-&gt;hasValidCredentials($user, $credentials)) {
            $this-&gt;login($user, $remember);

            return true;
        }
        //登陸失敗的話,能夠觸發事件通知用戶有可疑的登陸嘗試(須要本身定義listener來實現)
        $this-&gt;fireFailedEvent($user, $credentials);

        return false;
    }
    
    protected function hasValidCredentials($user, $credentials)
    {
        return ! is_null($user) &amp;&amp; $this-&gt;provider-&gt;validateCredentials($user, $credentials);
    }
}

class EloquentUserProvider implements UserProvider
{
    從數據庫中取出用戶實例
    public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &amp;&amp;
            array_key_exists('password', $credentials))) {
            return;
        }

        $query = $this-&gt;createModel()-&gt;newQuery();

        foreach ($credentials as $key =&gt; $value) {
            if (! Str::contains($key, 'password')) {
                $query-&gt;where($key, $value);
            }
        }

        return $query-&gt;first();
    }
    
    //經過給定用戶認證數據來驗證用戶
    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this-&gt;hasher-&gt;check($plain, $user-&gt;getAuthPassword());
    }
}

<h3>自定義用戶提供器</h3> <p>好了, 看到這裏就很明顯了, 咱們須要改爲本身的密碼驗證就是本身實現一下<code>validateCredentials</code>就能夠了, 修改<code>$this-&gt;hasher-&gt;check</code>爲咱們本身的密碼驗證規則。</p> <p>首先咱們來重寫<code>$user-&gt;getAuthPassword();</code> 在User模型中覆蓋其從父類中繼承來的這個方法,把數據庫中用戶表的<code>salt</code>和<code>password</code>傳遞到<code>validateCredentials</code>中來:</p>github

class user extends Authenticatable
{
    /**
     * 覆蓋Laravel中默認的getAuthPassword方法, 返回用戶的password和salt字段
     * @return array
     */
    public function getAuthPassword()
    {
        return ['password' =&gt; $this-&gt;attributes['password'], 'salt' =&gt; $this-&gt;attributes['salt']];
    }
}

<p>而後咱們用一個自定義的用戶提供器,經過它的<code>validateCredentials</code>來實現咱們本身系統的密碼驗證規則,因爲用戶提供器的其它方法不用改變沿用<code>EloquentUserProvider</code>裏的實現就能夠,因此咱們讓自定義的用戶提供器繼承自<code>EloquentUserProvider</code>:</p>web

namespace App\Foundation\Auth;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Str;

class CustomEloquentUserProvider extends EloquentUserProvider
{

    /**
     * Validate a user against the given credentials.
     *
     * @param \Illuminate\Contracts\Auth\Authenticatable $user
     * @param array $credentials
     */
    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        $plain = $credentials['password'];
        $authPassword = $user-&gt;getAuthPassword();

        return sha1($authPassword['salt'] . $plain) == $authPassword['password'];
    }
}

<p>接下來經過<code>Auth::provider()</code>將<code>CustomEloquentUserProvider</code>註冊到Laravel系統中,<code>Auth::provider</code>方法將一個返回用戶提供器對象的閉包做爲用戶提供器建立器以給定名稱註冊到Laravel中,代碼以下:</p>數據庫

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        \Auth::provider('custom-eloquent', function ($app, $config) {
            return New \App\Foundation\Auth\CustomEloquentUserProvider($app['hash'], $config['model']);
        });
    }
    ......
}

<p>註冊完用戶提供器後咱們就能夠在<code>config/auth.php</code>裏配置讓看守器使用新註冊的<code>custom-eloquent</code>做爲用戶提供器了:</p>json

//config/auth.php
'providers' =&gt; [
    'users' =&gt; [
        'driver' =&gt; 'coustom-eloquent',
        'model' =&gt; \App\User::class,
    ]
]

<h3>自定義認證看守器</h3> <p>好了,如今密碼認證已經修改過來了,如今用戶認證使用的看守器仍是<code>SessionGuard</code>, 在系統中會有對外提供API的模塊,在這種情形下咱們通常但願用戶登陸認證後會返回給客戶端一個JSON WEB TOKEN,每次調用接口時候經過這個token來認證請求接口的是不是有效用戶,這個需求須要咱們經過自定義的Guard擴展功能來完成,有個<code>composer</code>包<code>"tymon/jwt-auth": "dev-develop"</code>, 他的1.0beta版本帶的<code>JwtGuard</code>是一個實現了<code>Illuminate\Contracts\Auth\Guard</code>的看守器徹底符合我上面說的要求,因此咱們就經過<code>Auth::extend()</code>方法將<code>JwtGuard</code>註冊到系統中去:</p>segmentfault

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        \Auth::provider('custom-eloquent', function ($app, $config) {
            return New \App\Foundation\Auth\CustomEloquentUserProvider($app['hash'], $config['model']);
        });
        
        \Auth::extend('jwt', function ($app, $name, array $config) {
            // 返回一個 Illuminate\Contracts\Auth\Guard 實例...
            return new \Tymon\JWTAuth\JwtGuard(\Auth::createUserProvider($config['provider']));
        });
    }
    ......
}

<p>定義完以後,將 <code>auth.php</code> 配置文件的<code>guards</code>配置修改以下:</p>api

'guards' =&gt; [
    'web' =&gt; [
        'driver' =&gt; 'session',
        'provider' =&gt; 'users',
    ],

    'api' =&gt; [
        'driver' =&gt; 'jwt', // token ==&gt; jwt
        'provider' =&gt; 'users',
    ],
],

<p>接下來咱們定義一個API使用的登陸認證方法, 在認證中會使用上面註冊的<code>jwt</code>看守器來完成認證,認證完成後會返回一個JSON WEB TOKEN給客戶端</p>session

Route::post('apilogin', 'Auth\LoginController@apiLogin');
class LoginController extends Controller
{
   public function apiLogin(Request $request)
   {
        ...
    
        if ($token = $this-&gt;guard('api')-&gt;attempt($credentials)) {
            $return['status_code'] = 200;
            $return['message'] = '登陸成功';
            $response = \Response::json($return);
            $response-&gt;headers-&gt;set('Authorization', 'Bearer '. $token);
            return $response;
        }

    ...
    }
}

<h3>總結</h3> <p>經過上面的例子咱們講解了如何經過自定義認證看守器和用戶提供器擴展Laravel的用戶認證系統,目的是讓你們對Laravel的用戶認證系統有一個更好的理解知道在Laravel系統默認自帶的用戶認證方式沒法知足咱們的需求時如何經過自定義這兩個組件來擴展功能完成咱們項目本身的認證需求。</p> <p>本文已經收錄在系列文章<a href="https://github.com/kevinyan815/Learning_Laravel_Kernel" rel="nofollow noreferrer">Laravel源碼學習</a>裏,歡迎訪問閱讀。</p>閉包

原文地址:http://www.javashuo.com/article/p-ytdkwswn-de.html

相關文章
相關標籤/搜索