Laravel 使用者認證 Auth

bytecc發表於2020-01-13

很多應用是需要登陸後才能操作,Laravel提供了一個auth工具來實現使用者的認證功能。並且有一個config/auth.php來配置auth工具。大概看一下auth工具的常用方法

Auth::check();// 判斷當前使用者是否未登入
Auth::guest();// 判斷當前使用者是否未登入,與 check() 相反
Auth::guard();// 自定義看守器 預設為 `web`
Auth::user();// 獲取當前的認證使用者,一個提供者的模型
Auth::id();// 獲取當前的認證使用者的 ID(未登入情況下會報錯)
Auth::attempt(['email' => $email, 'password' => $password],true);// 透過給定的資訊來嘗試對使用者進行認證(成功後會自動啟動會話),第一個陣列就是認證的引數,第二個引數true就是'記住我'功能
Auth::login(User::find(1), $remember = false);// 登入一個指定使用者到應用上,一般是登陸的引數透過後,執行login方法,儲存session等登陸成功的操作
Auth::logout();// 使使用者退出登入(清除會話)

看一下auth.php的配置,其實Auth的實現原理就是透過一個guard來實現的

 'defaults' => [
        'guard' => 'web', //沒有指定guard時,就用‘web’
        'passwords' => 'users',
    ],
'guards' => [ //這就是guard陣列,用哪一個guard需要指定
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],
//每一個guard最少需要兩個成員,driver驅動(比如登陸成功,透過什麼方式儲存登陸狀態),provider提供者(就是使用者表,儲存使用者名稱密碼的那張表)

所以一個系統中,我們可以有多個認證體系,前後臺認證,介面認證等等,只要配置不同的guard即可。不同認證體系,使用對應的guard就可以。

說了這麼多,還是很懵逼,不怕,laravel給我們實現一個客戶認證腳手架,透過命令就可以實現一套認證系統。我們把這套系統弄懂,就可以仿照它的體系生成我們自己的認證體系。

laravel腳手架實現認證體系

php artisan make:auth

這個命令會做哪些動作?

1.生成登陸,註冊,退出等等的路由,在web.php新增Auth::routes();等價於下面

public function auth(array $options = [])
    {
        // Authentication Routes...
        $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
        $this->post('login', 'Auth\LoginController@login');
        $this->post('logout', 'Auth\LoginController@logout')->name('logout');
        // Registration Routes...
        if ($options['register'] ?? true) {
            $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
            $this->post('register', 'Auth\RegisterController@register');
        }
        // Password Reset Routes...
        if ($options['reset'] ?? true) {
            $this->resetPassword();
        }
        // Email Verification Routes...
        if ($options['verify'] ?? false) {
            $this->emailVerification();
        }
    }

其實就到呼叫Router::auth(),我們就可以看到登陸註冊的路由啦,其他功能類似。

2.生成登陸註冊等控制器App\Http\Controllers\Auth目錄下面,上面的路由就是訪問這裡的控制器

3.生成登陸註冊等頁面resource/views/auth/目錄下

4.生成provider表的migerate,建立使用者表,這時候你可以修改這個使用者表欄位

 public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }
php artisan migrate//生成user表

這樣,訪問上面的路由就可以生成一套認證體系了。認證系統其實就是三個步驟

1.登陸註冊等邏輯介面,就是控制器

2.配置路由

3.頁面

腳手架認證體系原理,我們自定義認證體系

先看註冊的邏輯,我們自定義自己的註冊類的時候,也可以參考著寫,跳轉路徑,檢查欄位,使用者表都可以根據實際情況修改。而介面,laravel已經寫在RegistersUsers裡面了。

class RegisterController extends Controller
{
    use RegistersUsers; //這個是laravel寫好的trait
    protected $redirectTo = '/home';//註冊成功跳轉路徑
    public function __construct()
    {
        $this->middleware('guest');//新增一個guest中介軟體
    }
    protected function validator(array $data)
    {
        return Validator::make($data, [ //返回一個過濾器
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }
    protected function create(array $data)
    {
        return User::create([  //註冊最終會寫入到provider中,透過create插入資料,返回模型
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
    protected function guard()//這個方法trait也有,但是如果我們用其他的guard,就要重寫方法
    {
        return Auth::guard('api');//你要使用的guard
    }

}

但是還是要看一下laravel是怎麼透過auth和auth的配置來實現註冊的

public function register(Request $request)
    {
        $this->validator($request->all())->validate();//判斷引數
        event(new Registered($user = $this->create($request->all())));//注意看create方法,這是插入到使用者表中,返回那個模型。
        $this->guard()->login($user);//獲取對應的guard,執行login方法,其實就是告訴session這個使用者登入成功狀態。(註冊成功,就不用重新登入),具體流程下面登入成功的操作再分析
        return $this->registered($request, $user)
                        ?: redirect($this->redirectPath());//註冊成功跳轉
    }

其實註冊步驟還是非常簡單的,引數正確,就可以寫入使用者表,註冊成功,然後跳轉。

登入邏輯

class LoginController extends Controller
{
   use AuthenticatesUsers;
    protected $redirectTo = '/home';//登入成功跳轉路徑
    public function __construct()
    {
        $this->middleware('guest')->except('logout');//新增guest中介軟體,除了logout方法
    }
    protected function guard()//這個方法trait也有,但是如果我們用其他的guard,就要重寫方法
    {
        return Auth::guard('api');//你要使用的guard
    }
}

登入介面laravel也幫我們實現了,直接use AuthenticatesUsers.我們看看

 public function login(Request $request)
    {
        $this->validateLogin($request);//驗證引數
        if ($this->hasTooManyLoginAttempts($request)) {//登入失敗次數,超過次數不能再登陸
            $this->fireLockoutEvent($request);
            return $this->sendLockoutResponse($request);
        }
        if ($this->attemptLogin($request)) { //比對資料庫,看看登陸是否成功
            return $this->sendLoginResponse($request);
        }
        $this->incrementLoginAttempts($request);//增加登陸失敗次數,以$this->username())),$request->ip()為基準,就是說同一個username,同一個ip登陸失敗次數是有限的。時間是1h.
        return $this->sendFailedLoginResponse($request);
    }

我們主要看他如何查使用者表,如何判斷登入是否成功

protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

和註冊一樣,也是獲取到對應的guard配置的Auth,執行attempt方法來驗證,所以不同的guard就要重寫guard方法,才能配置Auth.

Auth門面介紹

其實操作的類是Illuminate\Auth\AuthManager,而最終功能其實是對應的driver類實現的。

Auth::guard('web'),返回一個物件,就是那個driver物件

public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();
        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }
protected function resolve($name)
    {
        $config = $this->getConfig($name);
        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }
        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }
        throw new InvalidArgumentException(
            "Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
        );
    }

需要說明的,laravel給我們配置了三種guard的driver,分別是session,token,request,都放在Illuminate\Auth目錄下,先看一下SessionGuard

 public function __construct($name,UserProvider $provider,Session $session,Request rquest = null)
    {
        $this->name = $name;
        $this->session = $session;//操作session的工具
        $this->request = $request;//request
        $this->provider = $provider;//就是我們的provider模型
    }

有了這三個工具,我們就可以實現auth的各種功能了。

比如登陸attempt()

public function attempt(array $credentials = [], $remember = false)
    {
        $this->fireAttemptEvent($credentials, $remember);
        //用除了密碼查詢provider表,得到$user。
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
        //對比密碼是否正確
        if ($this->hasValidCredentials($user, $credentials)) {//嚴重
            $this->login($user, $remember);//成功就執行login方法
            return true;
        }
        $this->fireFailedEvent($user, $credentials);
        return false;
    }

比如獲取登陸的客戶模型Auth::user();

public function user()
    {
        if ($this->loggedOut) {
            return;
        }
        if (! is_null($this->user)) {
            return $this->user;
        }
        $id = $this->session->get($this->getName());//透過session獲取id
        if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
            $this->fireAuthenticatedEvent($this->user);
        }
        if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
            $this->user = $this->userFromRecaller($recaller);
            if ($this->user) {
                $this->updateSession($this->user->getAuthIdentifier());
                $this->fireLoginEvent($this->user, true);
            }
        }
        return $this->user;
    }

在看看TokenGuard,就是登陸成功會給前端儲存一個token,每次請求要帶這個token

Auth::user();

public function user()
    {
        // If we've already retrieved the user for the current request we can just
        // return it back immediately. We do not want to fetch the user data on
        // every call to this method because that would be tremendously slow.
        if (! is_null($this->user)) {
            return $this->user;
        }
        $user = null;
        $token = $this->getTokenForRequest();//從request得到token值
        if (! empty($token)) {
            $user = $this->provider->retrieveByCredentials([
                $this->storageKey => $this->hash ? hash('sha256', $token) : $token,
            ]);
        }
        return $this->user = $user;
    }
$this->storageKey 預設是"api_token",所以查詢provider表的條件是['api_token'=>$token],我們的使用者表需要一個api_token欄位儲存登陸的token值。

Auth中介軟體驗證登陸

我們知道,要讓介面透過登陸驗證才能訪問,需要在新增auth中介軟體,這個laravel已經配置好了

protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,//系統生成的前臺登陸驗證
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,        
        'auth.admin' => \App\Http\Middleware\AdminAuthMiddleware::class,// 後臺登陸驗證
    ];

這個Authenticate中介軟體繼承了Illuminate\Auth\Middleware\Authenticate類

看它的handle方法

 public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);
        return $next($request);
    }

protected function authenticate($request, array $guards)
    {
        if (empty($guards)) {
            $guards = [null];
        }
        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) { //Auth::check方法驗證
                return $this->auth->shouldUse($guard);
            }
        }
        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

自定義後臺驗證如何配置驗證中介軟體

比如我們建了一套後臺的登陸驗證系統,使用的guards是admin,我們的後臺路由就需要一箇中介軟體來驗證登陸了

php artisan make:middleware AdminAuth

把中介軟體寫入到路由中介軟體陣列中

protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,//系統生成的前臺登陸驗證
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,        
        'auth.admin' => \App\Http\Middleware\AdminAuthMiddleware::class,// 後臺登陸驗證
    ];

中介軟體的過濾程式碼

public function handle($request, Closure $next, $guard = null)
    {
        //當 auth 中介軟體判定某個使用者未認證,會返回一個 JSON 401 響應,或者,如果是 Ajax 請求的話,將使用者重定向到 login 命名路由(也就是登入頁面)。
        if (Auth::guard($guard)->guest()) {
            if ($request->ajax() || $request->wantsJson()) {
                return response('Unauthorized.', 401);
            } else {
                return redirect()->guest('admin/login');
            }
        }
        return $next($request);
    }

這裡中介軟體需要傳一個引數$guard,不傳就是web,所以說中介軟體的guard要和我們的登陸,註冊等系列介面要保持一致。假如我們建立了一個admin的guard,路由就應該這樣寫

 // 後臺其他頁面都要有登陸驗證
    Route::middleware('auth.admin:admin')->name('admin.')->group(function () {
        //後臺的其他路由
    }
本作品採用《CC 協議》,轉載必須註明作者和本文連結
用過哪些工具?為啥用這個工具(速度快,支援高併發...)?底層如何實現的?

相關文章