Laravel 文档阅读:认证

  • 原创
  • 发布时间: 1个月前
  • 收藏数: 0 / 点赞数: 0 / 阅读数: 19

想要快速开始?

在全新的 Laravel 项目中执行 Artisan 命令 php artisan make:auth 和 php artisan migrate 即可急速创建一个具有完整功能的认证系统脚手架代码。

之后,在浏览器地址栏输入 http://your-app.dev/register 就能进入注册页面,完整的路由列表请使用 php artisan route:list 命令在控制台查看。

简介


使用 Laravel 构建一个认证系统前所未有的简单,因为这个功能是开箱就准备好了的。

认证相关功能的配置文件是 config/app.php

作为核心功能,Laravel 的认证由「guards」和「providers」组成。

Guards 定义请求用户的认证方式。例如,使用了 session 驱动的 web Guard 就是用会话和 Cookie 的方式认证的。

Provider 定义从持久化存储设备里获得用户信息的方式,Laravel 开箱提供 Eloquent 和数据库 查询语句构造器 两种驱动方式的 Provider。你也可以自定义其他的 Provider。

如果你现在有些懵逼,没有关系!这些配置对于许多项目,都不需要修改,保持默认配置就行了。

数据库注意事项


Laravel 默认使用的 Provider 使用 Eloquent 驱动,对应的 Eloquent 模型是 App\User。如果你项目里不使用 Eloquent 驱动,你还可以修改为使用 database 驱动,它是基于查询语句构造器查询的。

App\User 模型对应的数据库表中,要确保密码字段至少是 60 个字符长度,当然,保持默认的 255 字符长度是个不错的选择。

同时,还要保证你的 users 表中包含一个可为空的、字符串类型(100 个字符长度)的 remember_token 字段,这个字段用来保存用户勾选「记住我」选项时的令牌值。

快速上手


在 Laravel 中已经预置了认证控制器,位于 App\Http\Controllers\Auth 目录下。

  • RegisterController 处理用户注册
  • LoginController 处理用户登录
  • ForgortPasswordController 发送密码重置链接
  • ResetPasswordController 处理重置密码逻辑。

每个控制器都使用了 trait 引入必要的实现方法。

对许多项目来说,你可以一点都不修改它们。

路由


执行下面这一条 Artisan 命令,就可以快速创建与认证功能相关的路由和视图文件。

php artisan make:auth

在一个全新的 Laravel 项目中,这条命令会生成布局、登录和注册视图,还有认证系统使用的所有路由,一个 HomeController(让用户登录之后跳转到面板页面)。

视图


使用 make:auth 创建的视图文件包括 resources/views/auth 目录下的。同时,还有一个布局文件 resources/views/layouts/app.balade.php

上面这些视图文件使用了 Boostrap 这个 CSS 框架,你也可以自定义使用的框架。

认证


现在路由、视图和控制器,数据库表都有了,开始用起来吧!

注册新用户,然后登录进去。

自定义跳转路径


当用户成功登录后,会跳转到 /home 地址,这个地址是在 LoginControllerRegisterController 和 ResetPasswordController 中的 redirectTo 属性中设定的,你可以修改它,以满足你的需求。

protected $redirectTo = '/';

如果在跳转时,还有一些通用的跳转逻辑要处理的话,那么就定义成一个 redirectTo 方法:

protected function redirectTo()
{
    return '/path';
}

redirectTo() 的优先级高于 redirectTo 属性。

自定义认证字段


Laravel 默认使用 email 作为认证关键字段,如果要自定义,在 LoginController 中定义一个 username 方法即可。

例如,下面我们将 name(用户名)字段作为认证关键字段:

public function username()
{
    return 'name';
}

自定义 Guard


你也可以在认证和注册用户时,使用自定义「Guard」。

然后在 LoginControllerRegisterController 和 ResetPasswordController 中的 guard 方法里使用它(还方法返回一个 Guard 实例对象)。

use Illuminate\Support\Facades\Auth;

protected function guard()
{
    return Auth::guard('guard-name');
}

自定义验证 / 存储


如果你需要修改注册页面的表单字段,或者要自定义保存用户到数据库里的逻辑,就需要修改 RegisterController 了。

RegisterController 的 validator 方法验证从前台注册页面传递过来的表单字段,这里定义了对应字段的取值规则。

你也可以按照需要修改这里定义的规则。

RegisterController 的 create 方法用来在数据库中创建一个新的 App\User 记录(也就是用户数据),使用 Eloquent ORM。你也可以根据需要修改。

访问认证用户


使用 Auth 门面访问认证用户。

use Illuminate\Support\Facades\Auth;

// 获得当前认证用户
$user = Auth::user();

// 获得当前认证用户的 ID
$user = Auth::id();

你也可以使用 Illuminate\Http\Request 实例访问认证用户。记住,在控制器方法里,Request 会被自动注入。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProfileController extends Controller
{
    /**
     * Update the user's profile.
     *
     * @param  Request  $request
     * @return Response
     */
    public function update(Request $request)
    {
        // $request->user() returns an instance of the authenticated user...
    }
}

判断是否是认证用户


如果用户登录了当前系统,那么 Auth 门面的 check 方法会返回 true,否则返回 false

use Illuminate\Support\Facades\Auth;

if (Auth::check) {
    // 用户已登录
}

虽然这种方式能判断用户登录与否,但是一般不这么做,而是在路由定义或者控制器构造函数中使用中间件,判断用户登录与否。

咱继续往下看。

保护路由


我们可以在定义路由的时候,顺便定义使用的中间件。Laravel 有一个 auth 中间件,在 Illuminate\Auth\Middleware\Authenticate 这个地方,它是在 App\Http\Kernel 中注册的,就可以用来判断用户的登录 / 认证了。

Route::get('profile', function () {
    // 只有认证用户才能进来
})->middleware('auth');

当然,你也可以在控制器构造函数里附加中间件。

public function __construct()
{
    $this->middleware('auth');
}

指定 Guard


为路由附加中间件的时候,可以为认证用户指定使用的 Guard,这里指定的 Guard 对应 auth.php 中 guards 数组选项中的 Key 之一。

public function __construct()
{
    $this->middleware('auth:api');
}

登录节流


如果你使用的是 Laravel 内置的 LoginController 类,它天然包含了 Illuminate\Foundation\Auth\ThrottlesLogins trait。

默认,如果用户经过几次失败的登录尝试后,只能再等 1 分钟后才能登录。

该节流限制是以用户的用户名 / 邮箱号地址和 IP 地址作为唯一标识的。

手动认证用户


如果你不用 Laravel 提供的认证控制器。那么你需要直接使用 Laravel 认证类写认证逻辑了。

不过别担心,不难。

我们通过 Auth 门面访问 Laravel 的认证服务,使用 attempt 方法。

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    /**
     * Handle an authentication attempt.
     *
     * @return Response
     */
    public function authenticate()
    {
        if (Auth::attempt(['email' => $email, 'password' => $hashedPassword])) {
            // Authentication passed...
            return redirect()->intended('dashboard');
        }
    }
}

attempt 方法接收的是验证字段键值对,提供的数据会用来在数据库表中查找。

在上面的例子里,我们在数据库里查询 email 字段,如果找到用户,就会将数据库中存储的哈希密码与提供的哈希密码比较,如果一样的话,用户登录成功。

用户登录成功,attempt 方法就会返回 true,否则返回 false

redirect() 函数返回一个 Redirector 实例对象,该对象的 intended 方法会在用户认证成功后,重定向到用户之前(未进入登录页面前)要去的那个 URL 地址。

也可以为 intended 方法添加一个参数 —— fallback URI —— 如果 intended 方法去到的那个 URL 无效,就使用这个 URL 地址。

指定额外条件


你也可以在调用 attempt 方法的时候添加额外要验证的字段。例如,登录时我们顺便检查用户的「激活」状态,只查询激活用户。

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // The user is active, not suspended, and exists.
}

这里登录使用的关键字段是 email,你可以在登录控制器中定义 username 方法,自行修改使用的关键字段。

访问特定的 Guard 实例


你可以在使用 Auth 门面的 guard 方法时,指定使用的 Guard。

就是说,你项目里的认证方式可以有好几种,因为每个 Guard 都有可能对应一种的认证方式,其背后对应的可能是一张数据库表或者是一个 Eloquent Model,它们完全可以单独组织成一个饱满的认证方式。

比如,我们在 auth.php 中定义了一个 admin Guard,与 web Guard 不同的是,admin Guard 是从 admin 数据库表里获取认证用户数据的,而 web Guard 是从 users 数据库表里获取认证用户数据的。

if (Auth::guard('admin')->attempt($credentials)) {
    //
}

注销


注销账号,使用 Auth 门面的 logout 方法。该方法会从从会话里清除用户认证信息。

Auth::logout();

记住用户


如果你需要提供「记住我」的功能,就要为 attempt 方法传递第二个布尔值参数,它会永远记住用户,直到用户手动注销。当然,users 表中必须包含一个 remember_token 列,用来保存「记住我」产生的令牌。

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
    // The user is being remembered...
}

如果使用内置的 LoginController 处理登录,那么已经具备了「记住我」功能,这是在引入的 trait 中定义的。

Auth 门面还提供一个 viaRemember 方法,用来判断认证用户是否是勾选了「记住我」功能的用户,如果是的话,就会返回 true,否则返回 false

if (Auth::viaRemember()) {
    //
}

其它认证方法


认证一个用户实例

如果你已经有一个用户实例了,那么把它带入 Auth::login 方法的登录系统。这是因为 App\User 模型实现了 Illuminate\Contracts\Auth\Authenticatable 这个接口,所以才可以这样用:

Auth::login($user);

// 登录然后「记住」用户
Auth::login($user, true);

当然,你也可以选择使用的 Guard 实例。

Auth::guard('admin')->login($user);

通过 ID 认证用户


如果已知用户主键 ID,也可以登录系统。使用 Auth::loginUsingId 方法,它接收的是用户主键。

Auth::loginUsingId(1);

// 登录然后「记住」用户呀
Auth::loginUsingId(1, true);

针对单次请求的用户认证


还可以使用 Auth::once 方法针对单次请求来认证用户,这种方式的认证不使用会话和 Cookie,这在构建无状态的 API 时非常有帮助:

if (Auth::once($credentials)) {
    //
}

HTTP 基本认证


HTTP 基本认证 提供了一种快速认证用户的方式(不需要额外的「登录」页面)。

为了使用它,在路由上附加一个 auth.basic 中间件即可,这个中间件在 Laravel 中已经注册好了,你直接用就行。

Route::get('profile', function () {
    // 只有认证用户才能进来   
})->middleware('auth.basic');

定义好后,当你在浏览器中访问这个路由的时候,会出现一个弹框,让你填入用户名和密码。默认,auth.basic 中间件使用 email 作为登录的关键字段,你可以在 LoginController 中添加 username 方法覆盖此约定。

关于 FastCGI 的注意事项


如果你是使用 PHP FastCGI,HTTP 基础认证可能会不生效。这时,将下面的两行配置内容添加到 .htaccess 文件即可:

RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

无状态 HTTP 基本认证


你也可以在不借助会话和 Cookie 的情况下使用 HTTP 基本认证,用来作为 API 认证使用,这称为「无状态 HTTP 基本认证」。

我们需要新定义一个中间件,在中间件里调用 Auth 门面的 onceBasic 方法,如果 onceBasic 没有返回响应,则允许请求的进一步处理。

<?php

namespace Illuminate\Auth\Middleware;

use Illuminate\Support\Facades\Auth;

class AuthenticateOnceWithBasicAuth
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, $next)
    {
        return Auth::onceBasic() ?: $next($request);
    }

}

接下里,我们在路由中使用该中间件:

Route::get('api/user', function () {
    // 只有认证用户才能进来   
})->middleware('auth.basic.once');

添加自定义 Guard 驱动


你可以使用 Auth 门面的 extend 方法自定义认证 Guard 驱动(需要传递 provider),这是在服务提供者中注册的。

比如,你就可以在现有的 AuthServiceProvider 中注册。

<?php

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']));
        });
    }
}

可以看到,extend 方法的回调函数里返回的是 lluminate\Contracts\Auth\Guard 的一个实现。

Guard 这个接口里定义的方法,在你的自定义 Guard 驱动中都必须实现。

之后,你就可以在 auth.php 配置文件中的 guards 选项中使用它了。

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

添加自定义用户 Provider


如果你不是使用关系型数据库保存用户数据,就需要实现自定义用户 Provider 驱动。

这里会用到 Auth 门面的 provider 方法:

<?php

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

然后就可以在 auth.php 配置文件中使用这个 Provider 驱动了。

不过,首先要定义一个使用这个驱动的 Provider。

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

然后在 guards 配置中使用这个 Provider。

UserProvider 接口


Illuminate\Contracts\Auth\UserProvider 的实现仅负责从持久化存储设备中获取 Illuminate\Contracts\Auth\Authenticatable 实现,这些持久化设备是指 MySQL,Riak 之类的系统。这两个接口允许 Laravel 认证机制,在无论用户数据如何被存储或使用什么类型的类来表示它的情况下,继续运行。

我们来看下 Illuminate\Contracts\Auth\UserProvider 接口:

<?php

namespace Illuminate\Contracts\Auth;

interface UserProvider {

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

}

retrieveById 方法接收的是用户主键,比如 MySQL 数据库里的自增 ID。最后返回匹配这个 ID 的 Authenticatable 的实现。

retrieveByToken 方法接收的是用户主键和「记住我」令牌(对应 remember_token 字段值)。和 retrieveById 方法一样,Authenticatable 的实现。

updateRememberToken 方法用新的 $token 值,更新 $user 的 remember_token 字段值。这个新的 $token 值可以来源于用户再次「记住我」的登录操作,或者是在注销账号时产生的。

retrieveByCredentials 接收的是一个数组,这个数组数据最终是传递给 Auth::attempt 方法的认证字段数据。这个方法会使用 where 条件查询子句,使用 $credentials['username'] 的值在持久化设备里查询,并且返回一个 Authenticatable 实现。此方法不应尝试进行任何密码验证或验证

validateCredentials 会比较 $user 和给定的 $credentials 数组来认证用户,此方法会使用 Hash::check 来比较 $user->getAuthPassword() 与 $credentials['password'] 的值,并且返回一个布尔值 true 或者 false,表示提供的认证数据是否正确。

Authenticatable 接口

现在我们来看 UserProvider 中的方法,先来看 Authenticatable 接口的方法。记住,Provider 应该从 retrieveById 和 retrieveByCredentials 方法中实现这个接口,

<?php

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

}

这个接口比较简单,getAuthIdentifierName 返回用户主键字段名;getAuthIdentifier 方法用户主键值。以 MySQL 作为后台数据库说明,主键就是自增主键;getAuthPassword 返回用户的哈希密码。此接口允许身份验证系统与任何 User 类一起使用,无论您正在使用什么 ORM 或存储抽象层。Laravel 内置的 App\User 模型就实现了这个接口,所以你可以用它作为的实现参考例子。

事件


在认证过程中,会触发许多事件,你可以在 EventServiceProvider 中为这些事件添加监听者。

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
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',
    ],
];
评论