Skip to content

认证

介绍

lightbulb

想快速开始吗? 只需在一个新的Laravel应用中运行php artisan make:authphp artisan migrate。然后,在浏览器中导航到http://your-app.test/register或分配给应用的其他URL。这两个命令将为您搭建整个认证系统!

Laravel使实现认证变得非常简单。实际上,几乎所有东西都为您开箱即用地配置好了。认证配置文件位于config/auth.php,其中包含了几个用于调整认证服务行为的详细选项。

在其核心,Laravel的认证功能由“守卫”和“提供者”组成。守卫定义了如何为每个请求认证用户。例如,Laravel附带一个session守卫,它使用会话存储和cookie来维护状态。

提供者定义了如何从持久存储中检索用户。Laravel支持使用Eloquent和数据库查询构建器检索用户。不过,您可以根据应用的需要定义其他提供者。

如果现在听起来有些混乱,不用担心!许多应用程序永远不需要修改默认的认证配置。

数据库注意事项

默认情况下,Laravel在您的app目录中包含一个App\User Eloquent模型。此模型可与默认的Eloquent认证驱动程序一起使用。如果您的应用程序不使用Eloquent,您可以使用database认证驱动程序,该驱动程序使用Laravel查询构建器。

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

此外,您应验证您的users(或等效)表包含一个可为空的字符串remember_token列,长度为100个字符。此列将用于存储选择“记住我”选项的用户的令牌。

认证快速入门

Laravel附带几个预构建的认证控制器,位于App\Http\Controllers\Auth命名空间中。RegisterController处理新用户注册,LoginController处理认证,ForgotPasswordController处理发送重置密码链接的电子邮件,ResetPasswordController包含重置密码的逻辑。每个控制器都使用一个trait来包含其必要的方法。对于许多应用程序,您不需要修改这些控制器。

路由

Laravel提供了一种快速搭建所有认证所需路由和视图的方法,只需一个简单的命令:

php
php artisan make:auth

此命令应在新应用程序上使用,将安装一个布局视图、注册和登录视图,以及所有认证端点的路由。还将生成一个HomeController来处理登录后请求到应用程序仪表板的请求。

lightbulb

如果您的应用程序不需要注册,您可以通过删除新创建的RegisterController并修改路由声明来禁用它:Auth::routes(['register' => false]);

视图

如前一节所述,php artisan make:auth命令将创建您需要的所有认证视图,并将其放置在resources/views/auth目录中。

make:auth命令还将创建一个resources/views/layouts目录,其中包含应用程序的基本布局。所有这些视图都使用Bootstrap CSS框架,但您可以根据需要自由定制。

认证

现在您已经为包含的认证控制器设置了路由和视图,您可以为应用程序注册和认证新用户了!您可以在浏览器中访问您的应用程序,因为认证控制器已经包含了认证现有用户和将新用户存储到数据库的逻辑(通过其traits)。

路径定制

当用户成功认证后,他们将被重定向到/home URI。您可以通过在LoginControllerRegisterControllerResetPasswordControllerVerificationController上定义一个redirectTo属性来定制认证后的重定向位置:

php
protected $redirectTo = '/';

接下来,您应修改RedirectIfAuthenticated中间件的handle方法,以在重定向用户时使用您的新URI。

如果重定向路径需要自定义生成逻辑,您可以定义一个redirectTo方法而不是redirectTo属性:

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

redirectTo方法将优先于redirectTo属性。

用户名定制

默认情况下,Laravel使用email字段进行认证。如果您想自定义此字段,可以在LoginController上定义一个username方法:

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

守卫定制

您还可以自定义用于认证和注册用户的“守卫”。要开始,请在LoginControllerRegisterControllerResetPasswordController上定义一个guard方法。该方法应返回一个守卫实例:

php
use Illuminate\Support\Facades\Auth;

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

验证/存储定制

要修改新用户注册时所需的表单字段,或自定义新用户如何存储到数据库中,您可以修改RegisterController类。此类负责验证和创建应用程序的新用户。

RegisterControllervalidator方法包含应用程序新用户的验证规则。您可以根据需要自由修改此方法。

RegisterControllercreate方法负责使用Eloquent ORM在数据库中创建新的App\User记录。您可以根据数据库的需要自由修改此方法。

检索已认证的用户

您可以通过Auth facade访问已认证的用户:

php
use Illuminate\Support\Facades\Auth;

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

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

或者,一旦用户认证,您可以通过Illuminate\Http\Request实例访问已认证的用户。请记住,类型提示的类将自动注入到控制器方法中:

php
<?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

php
use Illuminate\Support\Facades\Auth;

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

即使可以使用check方法确定用户是否已认证,您通常会使用中间件来验证用户在允许用户访问某些路由/控制器之前是否已认证。要了解更多信息,请查看保护路由的文档。

保护路由

路由中间件可用于仅允许已认证的用户访问给定路由。Laravel附带一个auth中间件,定义在Illuminate\Auth\Middleware\Authenticate中。由于此中间件已在您的HTTP内核中注册,您只需将中间件附加到路由定义即可:

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

如果您使用控制器,您可以从控制器的构造函数中调用middleware方法,而不是直接在路由定义中附加它:

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

重定向未认证用户

auth中间件检测到未授权用户时,它将用户重定向到login 命名路由。您可以通过更新app/Http/Middleware/Authenticate.php文件中的redirectTo函数来修改此行为:

php
/**
 * 获取用户应重定向到的路径。
 *
 * @param  \Illuminate\Http\Request  $request
 * @return string
 */
protected function redirectTo($request)
{
    return route('login');
}

指定守卫

在将auth中间件附加到路由时,您还可以指定应使用哪个守卫来认证用户。指定的守卫应对应于auth.php配置文件中guards数组中的一个键:

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

登录节流

如果您使用Laravel内置的LoginController类,Illuminate\Foundation\Auth\ThrottlesLogins trait将已包含在您的控制器中。默认情况下,如果用户在多次尝试后未能提供正确的凭据,他们将无法登录一分钟。节流是针对用户的用户名/电子邮件地址和他们的IP地址唯一的。

手动认证用户

请注意,您不需要使用Laravel附带的认证控制器。如果您选择删除这些控制器,您将需要直接使用Laravel认证类来管理用户认证。别担心,这很简单!

我们将通过Auth facade访问Laravel的认证服务,因此我们需要确保在类的顶部导入Auth facade。接下来,让我们看看attempt方法:

php
<?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值的密码进行哈希处理,因为框架会在将其与数据库中的哈希密码进行比较之前自动对其进行哈希处理。如果两个哈希密码匹配,将为用户启动一个已认证的会话。

如果认证成功,attempt方法将返回true。否则,将返回false

intended方法在重定向器上将用户重定向到他们在被认证中间件拦截之前尝试访问的URL。可以为此方法提供一个备用URI,以防预期的目的地不可用。

指定附加条件

如果您愿意,您还可以在认证查询中添加额外的条件,除了用户的电子邮件和密码。例如,我们可以验证用户是否标记为“active”:

php
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // 用户是活跃的,没有被暂停,并且存在。
}
exclamation

在这些示例中,email不是必需的选项,它仅用作示例。您应使用与数据库中“用户名”对应的列名。

访问特定守卫实例

您可以使用Auth facade上的guard方法指定要使用的守卫实例。这允许您使用完全独立的可认证模型或用户表来管理应用程序的不同部分的认证。

传递给guard方法的守卫名称应对应于auth.php配置文件中配置的一个守卫:

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

登出

要将用户从应用程序中登出,您可以使用Auth facade上的logout方法。这将清除用户会话中的认证信息:

php
Auth::logout();

记住用户

如果您希望在应用程序中提供“记住我”功能,可以将布尔值作为attempt方法的第二个参数传递,这将使用户无限期地保持认证状态,或直到他们手动登出。您的users表必须包含字符串remember_token列,该列将用于存储“记住我”令牌。

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

如果您使用Laravel附带的内置LoginController,控制器使用的traits已经实现了“记住”用户的正确逻辑。

如果您正在“记住”用户,您可以使用viaRemember方法来确定用户是否使用“记住我”cookie进行认证:

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

其他认证方法

认证用户实例

如果您需要将现有用户实例登录到应用程序中,可以使用用户实例调用login方法。给定的对象必须是Illuminate\Contracts\Auth\Authenticatable 契约的实现。Laravel附带的App\User模型已经实现了此接口:

php
Auth::login($user);

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

您可以指定要使用的守卫实例:

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

通过ID认证用户

要通过用户的ID将其登录到应用程序中,可以使用loginUsingId方法。此方法接受您希望认证的用户的主键:

php
Auth::loginUsingId(1);

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

一次性认证用户

您可以使用once方法将用户登录到应用程序中以进行单个请求。不会使用会话或cookie,这意味着此方法在构建无状态API时可能会有所帮助:

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

HTTP基本认证

HTTP基本认证提供了一种快速认证应用程序用户的方法,而无需设置专用的“登录”页面。要开始,请将auth.basic 中间件附加到您的路由。auth.basic中间件已包含在Laravel框架中,因此您不需要定义它:

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

一旦中间件附加到路由,您在浏览器中访问路由时将自动提示输入凭据。默认情况下,auth.basic中间件将使用用户记录上的email列作为“用户名”。

关于FastCGI的注意事项

如果您使用PHP FastCGI,HTTP基本认证可能无法开箱即用。应将以下行添加到您的.htaccess文件中:

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

无状态HTTP基本认证

您还可以使用HTTP基本认证而不在会话中设置用户标识符cookie,这对于API认证特别有用。为此,定义一个中间件来调用onceBasic方法。如果onceBasic方法未抛出异常,请求可以进一步传递到应用程序中:

php
<?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);
    }

}

接下来,注册路由中间件并将其附加到路由:

php
Route::get('api/user', function () {
    // 只有已认证的用户可以进入...
})->middleware('auth.basic.once');

登出

要手动将用户从应用程序中登出,您可以使用Auth facade上的logout方法。这将清除用户会话中的认证信息:

php
use Illuminate\Support\Facades\Auth;

Auth::logout();

在其他设备上使会话失效

Laravel还提供了一种机制,用于使用户在其他设备上活动的会话失效并“登出”,而不使其当前设备上的会话失效。在开始之前,您应确保Illuminate\Session\Middleware\AuthenticateSession中间件在您的app/Http/Kernel.php类的web中间件组中存在且未被注释掉:

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

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

php
use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($password);
exclamation

当调用logoutOtherDevices方法时,用户的其他会话将完全失效,这意味着他们将从之前认证的所有守卫中“登出”。

添加自定义守卫

您可以使用Auth facade上的extend方法定义自己的认证守卫。您应在服务提供者中放置此extend调用。由于Laravel已经附带一个AuthServiceProvider,我们可以将代码放在该提供者中:

php
<?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
{
    /**
     * 注册任何应用程序认证/授权服务。
     *
     * @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配置中使用此守卫:

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

闭包请求守卫

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

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

php
use App\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配置中使用此守卫:

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

添加自定义用户提供者

如果您不使用传统的关系数据库来存储用户,您将需要使用自己的认证用户提供者扩展Laravel。我们将使用Auth facade上的provider方法来定义自定义用户提供者:

php
<?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
{
    /**
     * 注册任何应用程序认证/授权服务。
     *
     * @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

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

最后,您可以在guards配置中使用此提供者:

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

用户提供者契约

Illuminate\Contracts\Auth\UserProvider实现仅负责从持久存储系统(如MySQL、Riak等)中获取Illuminate\Contracts\Auth\Authenticatable实现。这两个接口允许Laravel认证机制继续运行,而不管用户数据如何存储或使用什么类型的类来表示它。

让我们看看Illuminate\Contracts\Auth\UserProvider契约:

php
<?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函数通过其唯一的$identifier和“记住我”$token检索用户,存储在字段remember_token中。与前一个方法一样,应返回Authenticatable实现。

updateRememberToken方法使用新$token更新$user字段remember_token。在成功的“记住我”登录尝试或用户登出时分配新令牌。

retrieveByCredentials方法接收在尝试登录应用程序时传递给Auth::attempt方法的凭据数组。然后,该方法应“查询”底层持久存储以匹配这些凭据的用户。通常,此方法将运行一个带有$credentials['username']的“where”条件的查询。然后,该方法应返回Authenticatable的实现。此方法不应尝试进行任何密码验证或认证。

validateCredentials方法应将给定的$user$credentials进行比较以认证用户。例如,此方法可能应使用Hash::check$user->getAuthPassword()的值与$credentials['password']的值进行比较。此方法应返回truefalse,指示密码是否有效。

可认证契约

现在我们已经探讨了UserProvider上的每个方法,让我们看看Authenticatable契约。请记住,提供者应从retrieveByIdretrieveByTokenretrieveByCredentials方法返回此接口的实现:

php
<?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应返回用户的哈希密码。此接口允许认证系统与任何用户类一起工作,无论您使用什么ORM或存储抽象层。默认情况下,Laravel在app目录中包含一个User类,该类实现了此接口,因此您可以查看此类以获取实现示例。

事件

Laravel在认证过程中会触发各种事件。您可以在EventServiceProvider中附加监听器到这些事件:

php
/**
 * 应用程序的事件监听器映射。
 *
 * @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',
    ],
];