很多應用是需要登陸後才能操作,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 協議》,轉載必須註明作者和本文連結