Laravel 的 Auth::attempt () 初探及修改 bcrypt 驗證為 MD5

王舉發表於2017-09-07

如果你在使用Laravel的話,使用者的程式碼只需要一行程式碼就可以搞定

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // 驗證成功的邏輯
}

但是,如果你想切換為自定義的加密驗證方式,那麼這篇文章可能會給你一些思路
比如,如果我想把密碼的驗證方式更換為MD5,我應該怎麼做呢?
別急,先從laravel框架的驗證流程開始
我們呼叫的Auth::attempt()在哪裡實現的呢?
先從Auth找起
config/app.php

'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,
        'Auth' => Illuminate\Support\Facades\Auth::class,
                // ...
        ]

我們看到我們呼叫Auth其實是呼叫了

 Illuminate\Support\Facades\Auth::class

開啟這個類檔案

 class Auth extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'auth';
    }
    // ...
}

可以看到,Auth是透過Facade動態繫結的,繫結到哪裡呢,進一步尋找我們發現
vendor/laravel/framework/src/Illuminate/AuthServiceProvider

 class AuthServiceProvider extends ServiceProvider
{
    /**
     * Register the authenticator services.
     *
     * @return void
     */
    protected function registerAuthenticator()
    {
        $this->app->singleton('auth', function ($app) {
            $app['auth.loaded'] = true;
            return new AuthManager($app);
        });

        $this->app->singleton('auth.driver', function ($app) {
            return $app['auth']->guard();
        });
    }
}

預設的Auth繫結了AuthManager,開啟AuthManager檔案

 <?php
namespace Illuminate\Auth;

use Closure;
use InvalidArgumentException;
use Illuminate\Contracts\Auth\Factory as FactoryContract;
class AuthManager implements FactoryContract
{
    use CreatesUserProviders;

    protected $app;

    protected $guards = [];

    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return isset($this->guards[$name])
                    ? $this->guards[$name]
                    : $this->guards[$name] = $this->resolve($name);
    }

    public function getDefaultDriver()
    {
        return $this->app['config']['auth.defaults.guard'];
    }

    public function __call($method, $parameters)
    {

        return $this->guard()->{$method}(...$parameters);
    }
}

並沒有找到attempt方法,不過有一個__call的魔術方法,那肯定是他裡面沒錯了,為了快速找到他究竟是何方神聖,直接用

 dd(get_class($this->guard()));

真正的attempt究竟被誰呼叫了呢?
列印了SessionGuard,繼續找下去

Illuminate\Auth\SessionGuard

開啟該類,發現終於發現了我們尋找好久的attempt的實現

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

        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);

            return true;
        }
        $this->fireFailedEvent($user, $credentials);

        return false;
    }

這就是我們一直使用的attempt的實現,透過 $this->provider->retrieveByCredentials($credentials)獲取使用者資訊,並驗證,如果成功則登入,並返回true
所以我們真正做的密碼驗證肯定在retrieveByCredentials這個方法裡面
Laravel 預設提供了 UserProviderEloquentUserProvider
開啟改方法

class EloquentUserProvider implements UserProvider
{
    protected $hasher;

    protected $model;
    public function __construct(HasherContract $hasher, $model)
    {
        $this->model = $model;
        $this->hasher = $hasher;
    }
    public function validateCredentials(UserContract $user, array $credentials)
    {

        $plain = $credentials['password'];
        return $this->hasher->check($plain, $user->getAuthPassword());
    }
    public function setHasher(HasherContract $hasher)
    {
        $this->hasher = $hasher;

        return $this;
    }
}

所以這裡的hasher就是系統預設的BcryptHasher了,我們修改他直接注入自己的haser
ok,瞭解思路了,開始搞它

1.編寫自己的hasher

<?php

namespace App\Helpers\Hasher;

use Illuminate\Contracts\Hashing\Hasher;

class MD5Hasher implements Hasher
{
    public function check($value, $hashedValue, array $options = [])
    {

        return $this->make($value) === $hashedValue;
    }

    public function needsRehash($hashedValue, array $options = [])
    {
        return false;
    }

    public function make($value, array $options = [])
    {
        $value = env('SALT', '').$value;

        return md5($value);
    }

}

2.用自己的Hasher替換預設的Hasher

建立MD5HashServiceProvider

php artisan make:provider MD5HashServiceProvider

新增如下方法

<?php

namespace App\Providers;

use App\Helpers\Hasher\MD5Hasher;
use Illuminate\Support\ServiceProvider;

class MD5HashServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->app->singleton('hash', function () {
            return new MD5Hasher;
        });
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    public function provides()
    {
        return ['hash'];
    }
}

然後在config/app.phpproviders中,將

Illuminate\Hashing\HashServiceProvider::class,

替換為

\App\Providers\MD5HashServiceProvider::class,

OK,大功告成

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章