Laravel核心代碼學習 -- 擴展用戶認證系統

擴展用戶認證系統

上一節咱們介紹了Laravel Auth系統實現的一些細節知道了Laravel是如何應用看守器和用戶提供器來進行用戶認證的,可是針對咱們本身開發的項目或多或少地咱們都會須要在自帶的看守器和用戶提供器基礎之上作一些定製化來適應項目,本節我會列舉一個在作項目時遇到的具體案例,在這個案例中用自定義的看守器和用戶提供器來擴展了Laravel的用戶認證系統讓它能更適用於咱們本身開發的項目。php

在介紹用戶認證系統基礎的時候提到過Laravel自帶的註冊和登陸驗證用戶密碼時都是去驗證採用bcypt加密存儲的密碼,可是不少已經存在的老系統中用戶密碼都是用鹽值加明文密碼作哈希後存儲的,若是想要在這種老系統中應用Laravel開發項目的話那麼咱們就不可以再使用Laravel自帶的登陸和註冊方法了,下面咱們就經過實例看看應該如何擴展Laravel的用戶認證系統讓它可以知足咱們項目的認證需求。git

修改用戶註冊

首先咱們將用戶註冊時,用戶密碼的加密存儲的方式由bcypt加密後存儲改成由鹽值與明文密碼作哈希後再存儲的方式。這個很是簡單,上一節已經說過Laravel自帶的用戶註冊方法是怎麼實現了,這裏咱們直接將\App\Http\Controllers\Auth\RegisterController中的create方法修改成以下:github

/**
 * 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' => $data['email'],
        'password' => sha1($salt . $data['password']),
        'register_time' => time(),
        'register_ip' => ip2long(request()->ip()),
        'salt' => $salt
    ]);
}
複製代碼

上面改完後註冊用戶後就能按照咱們指定的方式來存儲用戶數據了,還有其餘一些須要的與用戶信息相關的字段也須要存儲到用戶表中去這裏就再也不贅述了。web

修改用戶登陸

上節分析Laravel默認登陸的實現細節時有說登陸認證的邏輯是經過SessionGuardattempt方法來實現的,在attempt方法中SessionGuard經過EloquentUserProviderretriveBycredentials方法從用戶表中查詢出用戶數據,經過 validateCredentials方法來驗證給定的用戶認證數據與從用戶表中查詢出來的用戶數據是否吻合。數據庫

下面直接給出以前講這塊時引用的代碼塊:json

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

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

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

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

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

        $query = $this->createModel()->newQuery();

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

        return $query->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->hasher->check($plain, $user->getAuthPassword());
    }
}
複製代碼

自定義用戶提供器

好了, 看到這裏就很明顯了, 咱們須要改爲本身的密碼驗證就是本身實現一下validateCredentials就能夠了, 修改$this->hasher->check爲咱們本身的密碼驗證規則。api

首先咱們來重寫$user->getAuthPassword(); 在User模型中覆蓋其從父類中繼承來的這個方法,把數據庫中用戶表的saltpassword傳遞到validateCredentials中來:bash

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

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

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->getAuthPassword();

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

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

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']);
        });
    }
    ......
}
複製代碼

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

//config/auth.php
'providers' => [
    'users' => [
        'driver' => 'coustom-eloquent',
        'model' => \App\User::class,
    ]
]
複製代碼

自定義認證看守器

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

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']));
        });
    }
    ......
}
複製代碼

定義完以後,將 auth.php 配置文件的guards配置修改以下:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'jwt', // token ==> jwt
        'provider' => 'users',
    ],
],
複製代碼

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

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

    ...
    }
}
複製代碼

總結

經過上面的例子咱們講解了如何經過自定義認證看守器和用戶提供器擴展Laravel的用戶認證系統,目的是讓你們對Laravel的用戶認證系統有一個更好的理解知道在Laravel系統默認自帶的用戶認證方式沒法知足咱們的需求時如何經過自定義這兩個組件來擴展功能完成咱們項目本身的認證需求。

本文已經收錄在系列文章Laravel源碼學習裏,歡迎訪問閱讀。

相關文章
相關標籤/搜索