Laravel Authentication 认证系统

Authentication 认证系统使用

Authentication
学习笔记《Laravel Auth 代码阅读》

花了两天研究 Laravel,昨天是通宵达旦搞到将近凌晨5点,基本掌握系统架构,可以说是博大精深!今天继续,主要心思放到整个 Laravel Auth 子系统中来了。

基本流程解读

HTTP本身是无状态,通常在系统交互的过程中,使用账号或者Token标识来确定认证用户,Token 一般会以 Cookie 方式保存到客户端,客户端发送请求时一并将 Token 发回服务器。如果客服端没有 Cookie,通常做法时时在 URL 中携带 Token。也可以 HTTP 头信息方式携带 Token 到服务器。Laravel 提供 Session 和 URL 中携带 token 两种方式做认证。对应配置文件中的 guards 配置的 web、api。

web 认证基于 Session 根据 SessionId 获取用户,provider 查询关联用户;api认证是基于token值交互,也采用users这个provider;

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],

    // 'users' => [
    //     'driver' => 'database',
    //     'table' => 'users',
    // ],
],

配置文件 /config/auth.php 中的 provider 是提供用户数据的接口,要标注驱动对象和目标对象,默认值 users是一套 provider 的名字,采用 eloquent 驱动,对应模类是 App\User。也可以使用 database 的方式。

Laravel 内置了认证模块,也可以手动安装,命令会生成相关的视图文件:

artisan make:auth

app
+-- Http
|   +-- Middleware
|   |   +-- RedirectIfAuthenticated.php
|   +-- Controllers
|       +-- Auth
|           +-- ForgotPasswordController.php
|           +-- LoginController.php
|           +-- RegisterController.php
|           +-- ResetPasswordController.php
+-- Providers
|   +-- AuthServiceProvider.php
+-- User.php

整个认证个模块包括相应的视图文件,一个 RedirectIfAuthenticated 中间件和四个 Controller 文件,一个 User 模型文件,它是 Authenticatable 接口类。另外还有一个 AuthServiceProvider 它是服务容器,为注入认证用户的数据提供者 UserProvider 接口准备的。密码数据是从 Authenticatable 流向 UserProvider 的,前者读取数据,后者负责校验。列如可以尝试这样覆盖 getAuthPassword() 方法来测试数据逻辑,而不是从数据库中读入数据:

public function getAuthPassword(){
    return bcrypt("userpass");
}

此方法原本是在 Illuminate\Auth\Authenticatable 定义的,它是 trait Authenticatable 类,是 PHP 多继承的规范。这个方法就是简单地返回 $this->password,即模型关联的数据表字段 password 的值,数据是通过依赖注入的。

app/Http/Kernel.php 注册中间件:

protected $routeMiddleware = [
    // ...
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
];

Auth模块从功能上分为用户认证和权限管理两个部分;

Illuminate\Auth是负责用户认证和权限管理的模块;
Illuminate\Foundation\Auth 提供了登录、修改密码、重置密码等一系统列具体逻辑实现;
Illuminate\Foundation\Auth\AuthenticatesUsers 负责登录视图逻辑
Illuminate\Auth\Passwords 目录下是密码重置或忘记密码处理的小模块;
config\auth.php认证相关配置文件

RedirectIfAuthenticated 只有 handle() 处理逻辑,但 Auth 类中并没有定义 Auth::guard(),这是经过 Facades 编程模式,通过 __callStatic() 关联到了 AuthManager,代码注解中有提示。Facade 是一套配合 Service Container 的静态方法解决方案,是一套设计得非常优雅的机制,是 Laravel 的核心机制之一。

public function handle($request, Closure $next, $guard = null)
{
    if (Auth::guard($guard)->check()) {
        return redirect('/home');
    }

    return $next($request);
}

通过获取配置文件的 guards 设置,不同的依赖就会加载进来:

HTTP Basic: /src/Illuminate/Auth/RequestGuard.php
Session: /src/Illuminate/Auth/SessionGuard.php
Token: /src/Illuminate/Auth/TokenGuard.php

这些按标准接口定义的类都有 user() 方法,这个方法就是从数据库获取匹配的授权用户,用户数据是通过 UserProvider 接口提供的,即 /app/Providers/AuthServiceProvider.php。通过实现此接口可以定制自己的认证逻辑,UserProvider::validateCredentials() 就是检验密码的方法。自带的 /app/User.php 模型就是实现了 Authenticatable 接口的类。

public function user()
{
    if ($this->loggedOut) {
        return;
    }

    if (! is_null($this->user)) {
        return $this->user;
    }

    $id = $this->session->get($this->getName());

    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;
}

/Illuminate/Routing/Router.php 中已经定义好和认证相关的一组路由,只需在 /routes/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();
}

public function resetPassword()
{
    $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
    $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
    $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
    $this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update');
}

public function emailVerification()
{
    $this->get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
    $this->get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
    $this->get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
}

来看第一条路由,是一条命名路由 name(),执行控制器的方法是 LoginController->showLoginForm() ,这个方法会调出视图 view('auth.login'),需要自己建立视图文件 \resources\views\auth\login.blade.php,可以使用命令 php artisan make:auth 来生成。登录表单大概是这样,@csrf 这是默认需要添加的参数,除非在 VerifyCsrfToken.php中间关闭了校验。



{{ csrf_field() }}
{{ __('E-Mail Address') }}:
{{ __('Password') }}:

所有post请求中必须包含一个 @crsf 的字段用以防止跨域攻击,只有通过验证才认为是安全的提交动作,否则会得到 419 Page Expired。打开 app\Http\Middleware\VerifyCsrfToken.php 添加排除规则即可关闭:

protected $except = [
    'login',
];

只是,LoginController 类并没有定义这个方法,这个方法定义在 AuthenticatesUsers 中定义的,通过 use 关键字引入 trait Authenticatable,这相当于 PHP 的多继承用法。

use AuthenticatesUsers;

emailpassword 两个变量用于匹配用户,密码生成使用的是 bcrypt() 方法,自带的 DatabaseSeeder.php 中含有初始化数据,按需要去执行命令填充到数据库 artisan db:seed

还有一些其他的认证方法:

Auth::check() 判断当前用户是否已认证(是否已登录)
Auth::user() 获取当前的认证用户
Auth::id() 获取当前的认证用户的 ID(未登录情况下会报错)
Auth::attempt(['email' => $email, 'password' => $password], $remember)尝试对用户进行认证
Auth::attempt($credentials, true) 通过传入 true 值来开启 '记住我' 功能
Auth::once($credentials) 只针对一次的请求来认证用户
Auth::login($user)
Auth::login($user, true) // Login and "remember" the given user...
Auth::login(User::find(1), $remember) 登录一个指定用户到应用上
Auth::guard('admin')->login($user)
Auth::loginUsingId(1) 登录指定用户 ID 的用户到应用上
Auth::loginUsingId(1, true) // Login and "remember" the given user...
Auth::logout() 使用户退出登录(清除会话)
Auth::validate($credentials) 验证用户凭证
Auth::viaRemember() 是否通过记住我登录
Auth::basic('username') 使用 HTTP Basic Auth 的基本认证方式来认证
Auth::onceBasic() 执行「HTTP Basic」登录尝试
Password::remind($credentials, function($message, $user){}) 发送密码重置提示给用户

Adding Custom Guards

namespace App\Providers;

use App\Services\Auth\JwtGuard;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Register any application authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::extend('jwt', function ($app, $name, array $config) {
            // Return an instance of Illuminate\Contracts\Auth\Guard...

            return new JwtGuard(Auth::createUserProvider($config['provider']));
        });
    }
}

/config/auth.php 中配置自定义的 Guards:

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

Closure Request Guards

use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

/**
 * Register any application authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Auth::viaRequest('custom-token', function ($request) {
        return User::where('token', $request->token)->first();
    });
}

/config/auth.php 中配置自定义的 Guards:

'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],

Adding Custom User Providers

如果不使用传统的关系数据库,可以自定义 Provider 可以实现自己的认证逻辑,如实现一个 riak Provider:

namespace App\Providers;

use Illuminate\Support\Facades\Auth;
use App\Extensions\RiakUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Register any application authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::provider('riak', function ($app, array $config) {
            // Return an instance of Illuminate\Contracts\Auth\UserProvider...

            return new RiakUserProvider($app->make('riak.connection'));
        });
    }
}

/config/auth.php 中配置自定义的 riak:

'providers' => [
    'users' => [
        'driver' => 'riak',
    ],
],

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

The User Provider Contract

Illuminate\Contracts\Auth\UserProvider contract 接口定义:

namespace Illuminate\Contracts\Auth;

interface UserProvider {

    public function retrieveById($identifier);
    public function retrieveByToken($identifier, $token);
    public function retrieveByCredentials(array $credentials);
    public function updateRememberToken(Authenticatable $user, $token);
    public function validateCredentials(Authenticatable $user, array $credentials);

}

retrieveById(), retrieveByToken(), and retrieveByCredentials() 方法返回的对象需要实现 Authenticatable 接口。

The Authenticatable Contract

namespace Illuminate\Contracts\Auth;

interface Authenticatable {

    public function getAuthIdentifierName();
    public function getAuthIdentifier();
    public function getAuthPassword();
    public function getRememberToken();
    public function setRememberToken($value);
    public function getRememberTokenName();

}

Events 认证事件

认证过程中(包括注册、忘记密码),定义了相关事件,实现自己的 EventServiceProvider 进行监听:

protected $listen = [
    'Illuminate\Auth\Events\Registered' => ['App\Listeners\LogRegisteredUser', ],
    'Illuminate\Auth\Events\Attempting' => ['App\Listeners\LogAuthenticationAttempt', ],
    'Illuminate\Auth\Events\Authenticated' => ['App\Listeners\LogAuthenticated', ],
    'Illuminate\Auth\Events\Login' => ['App\Listeners\LogSuccessfulLogin', ],
    'Illuminate\Auth\Events\Failed' => ['App\Listeners\LogFailedLogin', ],
    'Illuminate\Auth\Events\Logout' => ['App\Listeners\LogSuccessfulLogout', ],
    'Illuminate\Auth\Events\Lockout' => ['App\Listeners\LogLockout', ],
    'Illuminate\Auth\Events\PasswordReset' => ['App\Listeners\LogPasswordReset', ], 
];

你可能感兴趣的:(Laravel Authentication 认证系统)