用户认证

未匹配的标注
本文档最新版为 10.x,旧版本可能放弃维护,推荐阅读最新版!

用户认证

介绍

Laravel 使得实现身份验证非常简单。 事实上,几乎所有的配置都是现成的。 身份验证配置文件位于 config/auth.php, 其中包含几个有良好文档记录的选项,用于调整身份验证服务的行为。

在其核心,Laravel 的认证设施由「守卫」和「提供者」组成。守卫决定如何对每个请求的用户进行身份验证。比如,Laravel 带有一个 session 保护,它使用会话存储和 Cookies 来维护状态。

提供者决定如何从持久储存中检索用户。 Laravel 支持使用 Eloquent 和数据库查询生成器检索用户。但是,你可以根据应用程序的需要来自由定义其他提供者。

如果这些听起来让你有些疑惑,不要担心!许多应用程序永远不需要修改默认的身份验证配置。

快速开始

想要快速开始吗? 在一个全新的 Laravel 应用程序中安装 laravel/jetstream Composer 包然后运行 php artisan jetstream:install livewire 或者 php artisan jetstream:install inertia 。 迁移数据库后,将浏览器导航到 http://your-app.test/register 或分配给应用程序的任何其他 URL。这些命令将负责构建整个认证系统!

数据库注意事项

默认情况下, Laravel 包含一个 App\Models\User Eloquent model 在你的 app\Models 目录下。 这个模型可与默认的 Eloquent 身份验证驱动程序一起使用。如果你的应用程序没有使用 Eloquent,你可以用 database 身份验证驱动程序,它用的是 Laravel 查询生成器。

当为 App\Models\User 模型生成数据库架构时,确保密码的长度至少为 60 个字符。保持默认的字符串长度为 255 个字符是一个不错的选择。

另外,你应该验证 users(或等效)表是否包含一个可空的,含有 100 个字符的 remember_token 字符串。此列将用于存储用户登录应用程序时选择「记住我」选项的令牌。

用户认证快速指南

路由

Laravel 的 laravel/jetstream 扩展包提供了一种快速方法,可以使用一些简单的命令来支持你进行身份验证所需的所有路由和视图:

composer require laravel/jetstream

// 使用 Livewire 栈安装 Jetstream...
php artisan jetstream:install livewire

// 使用 Inertia 栈安装 Jetstream...
php artisan jetstream:install inertia

这个命令应该在新应用程序上使用,并将安装布局视图,注册和登录视图以及所有身份验证端点的路由。 还将生成一个 /dashboard 路由来处理应用程序仪表板的登录后请求。

了解更多有关 Jetstream,请访问官方的 Jetstream 文档

创建包含身份验证的应用程序

如果您正在开始一个全新的应用程序,并且希望包含身份验证脚手架,则可以在创建应用程序时使用 --jet 指令。 此命令将创建一个新的应用程序,并编译安装所有身份验证脚手架:

laravel new kitetail --jet

视图

如上一节所述,laravel/jetstream 包的 php artisan jetstream:install 命令将创建验证所需的所有视图,并将它们放在 resources/views/auth 目录下。

Jetstream 还将创建一个 resources/views/layouts 目录,其中包含应用程序的基本布局。 这些视图都使用 Tailwind CSS 框架,但是你可以按你的意愿自由地定制它们。

认证

现在你已经给身份验证控制器设置了路由和视图,你可以为应用程序注册和验证新用户了! 因为控制器已经默认包含了验证用户是否存在和保存用户到数据库中的认证逻辑(通过 traits 实现的),现在你已经可以在浏览器中访问应用了。

自定义路径

当用户认证成功,他们会被重定向到 /home 这个 URI 下。 你可以使用 RouteServiceProvider 中定义的 HOME 常量来自定义身份验证后的重定向路径:

public const HOME = '/home';

获取已认证的用户

你可以通过 Auth 门面获取已认证的用户:

use Illuminate\Support\Facades\Auth;

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

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

或者,你可以通过 Illuminate\Http\Request 实例来访问已认证的用户。别忘了,类型提示的类会被自动注入到你的控制器方法中:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProfileController extends Controller
{
    /**
     * 更新用户的资料
     *
     * @param  Request  $request
     * @return Response
     */
    public function update(Request $request)
    {
        // $request->user() 返回一个认证用户实例...
    }
}

确定当前用户是否已经认证

你可以使用 Auth facade 的 check 方法来检查用户是否已认证。如果已认证,将会返回 true

use Illuminate\Support\Facades\Auth;

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

提示:虽然可以使用 check 方法确认用户是否被认证,但是在允许用户访问的某些路由 / 控制器之前,通常还是会使用中间件来验证用户是否进行过身份验证。想要了解更多信息,请查看有关 保护路由 的文档。

保护路由

路由中间件 可以用于只允许通过认证的用户访问给定的路由。Laravel 自带了一个 auth 中间件,它定义在 Illuminate\Auth\Middleware\Authenticate 中。由于这个中间件已经在 HTTP 内核中注册,你只需把这个中间件附加到路由定义中:

Route::get('profile', function () {
    // 只有认证过的用户可以进入...
})->middleware('auth');

当然,如果你使用 控制器,你可以在控制器的构造函数中调用 middleware 方法来直接将其附加到路由定义中:

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

重定向未认证的用户

auth 中间件检测到一个未认证用户时,它会把用户重定向到名为 login命名路由上。
您可以通过修改 app/Http/Middleware/Authenticate.php 文件中的 redirectTo 函数来修改此行为:

/**
 * 获得用户跳转路径
 *
 * @param  \Illuminate\Http\Request  $request
 * @return string
 */
protected function redirectTo($request)
{
    return route('login');
}

指定看守器

当你把 auth 中间件添加到路由中时,同时也能指定使用哪个看守器进行用户认证。指定的看守器应该对应 auth.php 配置文件中 guards 数组中的的一个键:

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

密码确认

在某些情况下,您可能希望要求用户在访问应用程序的特定区域之前确认其密码。 例如,您可能需要先执行此操作,然后用户才能修改应用程序中的账单设置。

为此,Laravel提供了一个 password.confirm 中间件。 将 password.confirm 中间件添加到路由会将用户重定向到需要确认密码才能继续的页面:

Route::get('/settings/security', function () {
    // 用户再继续前需要确认密码...
})->middleware(['auth', 'password.confirm']);

用户成功确认密码后,会将用户重定向到他们最初尝试访问的路由。 默认情况下,确认密码后,用户将不必在三个小时内再次确认密码。 你可以使用 auth.password_timeout 配置选项自定义用户需要重新确认密码的时间长度。

登录限流

如果你使用 Laravel Jetstream,速率限制将自动应用于登录尝试。 默认情况下,如果用户多次尝试却无法提供正确的登录凭据,那么该用户在一分钟内将不能再次尝试登录。这种限流策略基于用户的用户名 / 邮箱地址及其 IP 地址的唯一性。

手动验证用户

请注意,你不是必须使用 Laravel Jetstream 附带的身份验证控制器。如果选择删除这些控制器,就需要直接使用 Lavarel 验证类。别担心,这很容易!

可以借助 Auth facade 访问 Laravel 服务,因此需要在类的开头导入 Auth 。下面来看看 attempt 方法:

<?php

namespace App\Http\Controllers;

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

class LoginController extends Controller
{
    /**
     * 处理认证尝试
     *
     * @param  \Illuminate\Http\Request $request
     *
     * @return Response
     */
    public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if (Auth::attempt($credentials)) {
            // 认证通过...
            return redirect()->intended('dashboard');
        }
    }
}

attempt 方法的每个参数是一个关联数组。数组值用于在数据库中查找用户。在上面的例子中,将通过 email 列的值查找用户。如果找到该用户,将用存储在数据库中的哈希密码与数组中的 password 值做比较。不需要对 password 做哈希运算,框架在与数据库中的哈希密码做比较前自动对此值做哈希运算。如果两个哈希值匹配,将为该用户建立验证通过的 session。

如果验证成功, attempt 方法返回 true ,否则返回 false

重定向中的 intended 方法将经由身份验证中间件将用户重定向到身份验证前截获的 URL 。如果预期目标不存在,可以为此方法指定一个回退 URI 。

指定额外条件

除了用户的电子邮件和密码之外,还可以向身份验证查询添加其他条件。例如, 我们可以验证用户是不是已经被标记为 「激活」:

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // 验证处于激活状态,并且存在的用户
}

注意:在这些例子中, email 不是必须的选项,它只用来做示范。你应该使用与你的数据库中 「用户名」 对应的列名。

访问指定的看守器实例

可以使用 Auth facade 的 guard方法指定想要使用的看守器实例。这允许你使用完全独立的可验证模型或用户表来管理应用程序各个部分的验证。

传递给 guard 方法的看守器名称需要与 auth.php 配置中的配置项之一相匹配:

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

登出

用户登出需要使用 Auth facade 的 logout 方法。它会清除用户会话(session)中的用户验证信息:

Auth::logout();

记住用户

如果想在应用中提供 「记住我」功能,可以给 attempt 方法传递一个布尔值作为其第二个参数,这会无限期保持用户身份验证,直到用户手动登出。用户表需要包含字符串类型的 remember_token 列用于存储令牌。

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
    // 用户已经被记住...
}

如果启用了「记住用户」,可以使用 viaRemember 方法判断是否使用了「记住我」cookie 对用户做身份验证:

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

其它身份验证方法

验证用户实例

如果要将已经存在的用户登入应用,可以调用 login 方法,并以用户实例作为其参数 。该对象必须实现 Illuminate\Contracts\Auth\Authenticatable 契约 。Laravel 自带的 App\Models\User 模型已经实现了这个接口:

Auth::login($user);

// 登录并记住给定用户...
Auth::login($user, true);

你可以指定想使用的看守器实例:

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

通过 ID 验证用户身份

可以使用 loginUsingId 方法通过 ID 将用户登录到应用。这个方法接受希望验证身份用户的主键:

Auth::loginUsingId(1);

// 登录并记住给定用户...
Auth::loginUsingId(1, true);

仅验证一次用户身份

可以使用 once 方法在单次请求中将用户登录到应用中。这样做将不使用 session 或 cookies,这意味着此方法有助于构建一个无状态 API:

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

HTTP 基础认证

HTTP 基础认证 提供了一种快速方法来验证你应用程序中的用户,而无需设置专用的「登录」页面。 开始之前, 先把 auth.basic 中间件 附加到你的路由中。auth.basic 中间件已包含在 Laravel 框架中,所以你不需要定义它:

Route::get('profile', function () {
    // 只有经过身份验证的用户才能进进入...
})->middleware('auth.basic');

将中间件附加到路由后,在浏览器中访问此路由时将自动提示您输入凭据。默认的,auth.basic 中间件把用户记录上的 email 字段 作为「用户名」。

FastCGI 的注意事项

如果你正使用 PHP FastCGI 模式,HTTP 基础认证可能无法正常工作。需要把下面几行添加到你的 .htaccess 文件中:

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

无状态 HTTP 基础认证

你也可以使用 HTTP 基础身份验证,而无需在会话中设置用户标识符 cookie,这对 API 的身份验证特别有用。为此 ,请 定义一个中间件 它将调用 onceBasic 方法。如果 onceBasic 方法没有返回任何响应,那么请求就可以进一步传递到应用程序中:

<?php

namespace App\Http\Middleware;

use Illuminate\Support\Facades\Auth;

class AuthenticateOnceWithBasicAuth
{
    /**
     * 处理传入的请求
     *
     * @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');

退出

要手动把用户从应用中退出登录,你可以使用 Auth facade 上的 logout 方法。这将清除用户会话中的身份认证信息:

use Illuminate\Support\Facades\Auth;

Auth::logout();

让其它设备上的 Session 失效

Laravel 还提供了一种机制,用于将其它设备上的用户 Session 失效和「注销」,而不会使当前设备上的 Session 失效。 当你想要用户更改或更新密码时可以使其他设备上的会话无效并且保持当前设备的登录状态时,可以使用此功能。

在开始之前,你应该确保 Illuminate\Session\Middleware\AuthenticateSession 在你的 app / Http / Kernel.php 类中的 web 中间件组中存在并且没有被注释掉:

'web' => [
    // ...
    \Illuminate\Session\Middleware\AuthenticateSession::class,
    // ...
],

然后, 你就可以使用 Auth facade 上的 logoutOtherDevices 方法。此方法要求用户提供其当前密码,你的应用程序应通过输入表单接受该密码:

use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($password);

当调用 logoutOtherDevices 方法时,用户的其他 Session 将完全失效,这意味着它们将「退出」他们之前通过身份认证的所有看守器。

注意:将 AuthenticateSession 中间件与login路由的自定义路由名结合使用时,必须覆盖应用程序异常处理程序上的 unauthenticated 方法,以正确地将用户重定向到登录页面。

添加自定义的看守器

你可以使用 Auth facade 的 extend 方法来定义自己的身份验证看守器。你应该在 服务提供器 中调用 extend 方法。由于 Laravel 已经附带了 AuthServiceProvider,我们可以将代码放在该提供器中:

<?php

namespace App\Providers;

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

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 注册任何应用程序验证/授权服务
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::extend('jwt', function ($app, $name, array $config) {
            // 返回一个 Illuminate\Contracts\Auth\Guard 实例...

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

正如你在上面的示例中所看到的,传递给 extend 方法的回调应该返回一个实现 Illuminate\Contracts\Auth\Guard 接口的实例。这个接口包含了一些你需要在自定义的看守器中实现的方法。当你的自定义看守器定义完成之后,你可以在 auth.php 配置文件的 guards 配置中使用这个看守器:

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

闭包请求看守器

实现基于 HTTP 请求的自定义身份验证系统的最简单方法,是使用 Auth::viaRequest 方法。此方法允许您使用单个闭包来快速定义身份验证过程。

首先,在 AuthServiceProviderboot 方法中调用 Auth::viaRequest 方法。viaRequest 方法接受一个看守器名称作为其第一个参数。此名称可以是描述你自定义看守器的任何字符串。传递给该方法的第二个参数应该是一个闭包函数,它接收传入的 HTTP 请求并返回一个用户实例,或者,如果验证失败,则为 null

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

/**
 * 注册任意应用认证/授权服务
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

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

当你完成了自定义看守器后,就可以在 auth.php 配置文件的 guards 配置中使用这个看守器:

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

添加自定义用户提供者

如果不使用传统的关系数据库存储用户,就需要使用自己的身份验证用户提供者扩展 Lavarel。可以使用 Auth facade 的 provider 方法自定义用户提供器:

<?php

namespace App\Providers;

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

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 注册任意应用身份验证 / 授权服务
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Auth::provider('riak', function ($app, array $config) {
            // 返回 Illuminate\Contracts\Auth\UserProvider... 实例...

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

一旦使用 provider 方法注册完毕,就可以在 auth.php 配置文件中切换到新的用户提供者。先定义一个使用新驱动的 provider

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

最后,你就可以在 guards 配置中使用这个提供者:

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

用户提供者契约

Illuminate\Contracts\Auth\UserProvider 实现仅负责从 MySQL、Riak 等持久化存储系统中提取 Illuminate\Contracts\Auth\Authenticatable 实现。无论用户如何存储及用于表示它的类是什么类型,这两个接口都允许 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 函数通常接受用于表示类的 key ,如 MySQL 数据库中自动递增的 ID 作为参数,并获取和返回与这个 ID 匹配的 Authenticatable 实现。

retrieveByToken 函数通过用户的唯一 $identifier 和存储在 remember_token 列的 「记住我」 令牌获取用户。与前一方法相同,它返回 Authenticatable 实现。

updateRememberToken 方法用新 $token 更新 $userremember_token 列。在「记住我」登录校验成功或者用户登出时分配「刷新令牌」。

在尝试登录到应用时,retrieveByCredentials 方法接受凭证数组传递给 Auth::attempt 方法。此方法在底层持久化存储中「查询」与这些凭证匹配的用户。通常,此方法运行一个基于 $credentials['username'] 的 「where」 条件,它应该返回一个 Authenticatable 实现。此方法不就尝试进行任何密码校验或身份验证。

validateCredentials 方法应该比较给定的 $user$credentials 来验证用户身份。例如,此方法或许应该使用 Hash::check 来比较 $user->getAuthPassword() 的值与 $credentials['password'] 的值。它应该返回 truefalse ,以表明用户密码是否有效。

身份验证契约

我们已经剖析了 UserProvider 的每个方法。下面再来看看 Authenticatable 契约。切记,用户提供器的 retrieveByIdretrieveByTokenretrieveByCredentials 方法将返回此接口的实例:

<?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\Models 目录会包含一个实现了此接口的 User 类,你可以以这个实现示例作为参考。

事件

在身份验证处理过程中 Laravel 引发了多种 事件 。 可以在 EventServiceProvider 中附着这些事件的监听器:

/**
 * 应用的事件监听器映射
 *
 * @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\Validated' => [
        'App\Listeners\LogValidated',
    ],

    'Illuminate\Auth\Events\Verified' => [
        'App\Listeners\LogVerified',
    ],

    'Illuminate\Auth\Events\Logout' => [
        'App\Listeners\LogSuccessfulLogout',
    ],

    'Illuminate\Auth\Events\CurrentDeviceLogout' => [
        'App\Listeners\LogCurrentDeviceLogout',
    ],

    'Illuminate\Auth\Events\OtherDeviceLogout' => [
        'App\Listeners\LogOtherDeviceLogout',
    ],

    'Illuminate\Auth\Events\Lockout' => [
        'App\Listeners\LogLockout',
    ],

    'Illuminate\Auth\Events\PasswordReset' => [
        'App\Listeners\LogPasswordReset',
    ],
];

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/8.x/aut...

译文地址:https://learnku.com/docs/laravel/8.x/aut...

上一篇 下一篇
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:3
讨论数量: 6
发起讨论 只看当前版本


else
Auth::login (); 在其他控制器获取不到用户信息?
1 个点赞 | 8 个回复 | 问答 | 课程版本 5.6
panco
这个真的有用吗?
0 个点赞 | 2 个回复 | 问答 | 课程版本 5.7
qinplain
Laravel 自定义登陆
0 个点赞 | 1 个回复 | 分享 | 课程版本 5.5
junqiang
laravel 8.* 用户认证 vite指令不生效
0 个点赞 | 0 个回复 | 分享 | 课程版本 8.x
myoppo
登录后访问 login 的重定向问题
0 个点赞 | 0 个回复 | 问答 | 课程版本 5.5