认证
介绍
想快速开始吗? 只需在一个新的 Laravel 应用中运行 php artisan make:auth
和 php artisan migrate
。然后,在浏览器中导航到 http://your-app.test/register
或分配给应用的其他 URL。这两个命令将为您搭建整个认证系统!
Laravel 使实现认证变得非常简单。实际上,几乎所有内容都为您开箱即用地配置好了。认证配置文件位于 config/auth.php
,其中包含了多个文档良好的选项,用于调整认证服务的行为。
在其核心,Laravel 的认证功能由“守卫”和“提供者”组成。守卫定义了如何为每个请求认证用户。例如,Laravel 附带一个 session
守卫,它使用会话存储和 cookies 来维护状态。
提供者定义了如何从持久存储中检索用户。Laravel 支持使用 Eloquent 和数据库查询构建器检索用户。不过,您可以根据应用的需要定义其他提供者。
如果现在这些听起来很复杂,不用担心!许多应用程序永远不需要修改默认的认证配置。
数据库注意事项
默认情况下,Laravel 在您的 app
目录中包含一个 App\User
Eloquent 模型。此模型可与默认的 Eloquent 认证驱动程序一起使用。如果您的应用程序不使用 Eloquent,您可以使用 database
认证驱动程序,该驱动程序使用 Laravel 查询构建器。
在为 App\User
模型构建数据库架构时,请确保密码列至少为 60 个字符长。保持默认的字符串列长度为 255 个字符是一个不错的选择。
此外,您应验证 users
(或等效)表包含一个可为空的、100 个字符的字符串 remember_token
列。此列将用于存储选择“记住我”选项登录到应用程序的用户的令牌。
认证快速入门
Laravel 附带几个预构建的认证控制器,位于 App\Http\Controllers\Auth
命名空间中。RegisterController
处理新用户注册,LoginController
处理认证,ForgotPasswordController
处理发送重置密码链接的电子邮件,ResetPasswordController
包含重置密码的逻辑。每个控制器都使用一个 trait 来包含其必要的方法。对于许多应用程序,您无需修改这些控制器。
路由
Laravel 提供了一种快速搭建所有认证所需路由和视图的方法,只需一个简单的命令:
php artisan make:auth
此命令应在新应用程序上使用,并将安装一个布局视图、注册和登录视图,以及所有认证端点的路由。还将生成一个 HomeController
来处理登录后请求到应用程序仪表板的请求。
视图
如前一节所述,php artisan make:auth
命令将创建您需要的所有认证视图,并将其放置在 resources/views/auth
目录中。
make:auth
命令还将在 resources/views/layouts
目录中创建一个应用程序的基本布局。所有这些视图都使用 Bootstrap CSS 框架,但您可以根据需要自由定制它们。
认证
现在您已经为包含的认证控制器设置了路由和视图,您可以为应用程序注册和认证新用户了!您可以在浏览器中访问您的应用程序,因为认证控制器已经包含了通过其 trait 认证现有用户并将新用户存储到数据库中的逻辑。
路径定制
当用户成功认证后,他们将被重定向到 /home
URI。您可以通过在 LoginController
、RegisterController
和 ResetPasswordController
上定义 redirectTo
属性来自定义认证后的重定向位置:
protected $redirectTo = '/';
接下来,您应修改 RedirectIfAuthenticated
中间件的 handle
方法,以在重定向用户时使用您的新 URI。
如果重定向路径需要自定义生成逻辑,您可以定义一个 redirectTo
方法而不是 redirectTo
属性:
protected function redirectTo()
{
return '/path';
}
redirectTo
方法将优先于 redirectTo
属性。
用户名定制
默认情况下,Laravel 使用 email
字段进行认证。如果您想自定义此项,可以在 LoginController
上定义一个 username
方法:
public function username()
{
return 'username';
}
守卫定制
您还可以自定义用于认证和注册用户的“守卫”。要开始,请在 LoginController
、RegisterController
和 ResetPasswordController
上定义一个 guard
方法。该方法应返回一个守卫实例:
use Illuminate\Support\Facades\Auth;
protected function guard()
{
return Auth::guard('guard-name');
}
验证/存储定制
要修改新用户注册时应用程序所需的表单字段,或自定义新用户如何存储到数据库中,您可以修改 RegisterController
类。此类负责验证和创建应用程序的新用户。
RegisterController
的 validator
方法包含应用程序新用户的验证规则。您可以根据需要自由修改此方法。
RegisterController
的 create
方法负责使用 Eloquent ORM 在数据库中创建新的 App\User
记录。您可以根据数据库的需要自由修改此方法。
检索已认证的用户
您可以通过 Auth
facade 访问已认证的用户:
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
中间件检测到未授权用户时,它将返回 JSON 401
响应,或者,如果请求不是 AJAX 请求,则将用户重定向到 login
命名路由。
您可以通过在 app/Exceptions/Handler.php
文件中定义一个 unauthenticated
函数来修改此行为:
use Illuminate\Auth\AuthenticationException;
protected function unauthenticated($request, AuthenticationException $exception)
{
return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest(route('login'));
}
指定守卫
在将 auth
中间件附加到路由时,您还可以指定应使用哪个守卫来认证用户。指定的守卫应对应于 auth.php
配置文件中的 guards
数组中的一个键:
public function __construct()
{
$this->middleware('auth:api');
}
登录节流
如果您使用 Laravel 的内置 LoginController
类,Illuminate\Foundation\Auth\ThrottlesLogins
trait 将已包含在您的控制器中。默认情况下,如果用户在多次尝试后未能提供正确的凭据,则用户将无法登录一分钟。节流是针对用户的用户名/电子邮件地址和他们的 IP 地址唯一的。
手动认证用户
当然,您不必使用 Laravel 附带的认证控制器。如果您选择删除这些控制器,您将需要直接使用 Laravel 认证类来管理用户认证。别担心,这很简单!
我们将通过 Auth
facade 访问 Laravel 的认证服务,因此我们需要确保在类的顶部导入 Auth
facade。接下来,让我们看看 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
值的密码进行哈希处理,因为框架会在将其与数据库中的哈希密码进行比较之前自动对其进行哈希处理。如果两个哈希密码匹配,将为用户启动一个已认证的会话。
如果认证成功,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
方法。这将清除用户会话中的认证信息:
Auth::logout();
记住用户
如果您希望在应用程序中提供“记住我”功能,您可以将布尔值作为 attempt
方法的第二个参数传递,这将使用户无限期地保持认证状态,或者直到他们手动登出。当然,您的 users
表必须包含字符串 remember_token
列,该列将用于存储“记住我”令牌。
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// 用户正在被记住...
}
如果您使用 Laravel 附带的内置 LoginController
,控制器使用的 trait 已经实现了“记住”用户的正确逻辑。
如果您正在“记住”用户,您可以使用 viaRemember
方法来确定用户是否使用“记住我”cookie 进行认证:
if (Auth::viaRemember()) {
//
}
其他认证方法
认证用户实例
如果您需要将现有用户实例登录到应用程序中,您可以使用用户实例调用 login
方法。给定的对象必须是 Illuminate\Contracts\Auth\Authenticatable
契约 的实现。当然,Laravel 附带的 App\User
模型已经实现了此接口:
Auth::login($user);
// 登录并“记住”给定用户...
Auth::login($user, true);
当然,您可以指定要使用的守卫实例:
Auth::guard('admin')->login($user);
通过 ID 认证用户
要通过用户的 ID 将用户登录到应用程序中,您可以使用 loginUsingId
方法。此方法接受您希望认证的用户的主键:
Auth::loginUsingId(1);
// 登录并“记住”给定用户...
Auth::loginUsingId(1, true);
一次性认证用户
您可以使用 once
方法在应用程序中为单个请求登录用户。不会使用会话或 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();
在其他设备上使会话失效
Laravel 还提供了一种机制,用于使用户在其他设备上活动的会话失效并“登出”,而不使他们当前设备上的会话失效。在开始之前,您应确保 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
方法时,用户的其他会话将完全失效,这意味着他们将从之前认证的所有守卫中“登出”。
添加自定义守卫
您可以使用 Auth
facade 上的 extend
方法定义自己的认证守卫。您应在 服务提供者 中放置此 extend
调用。由于 Laravel 已经附带了一个 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
{
/**
* 注册任何应用程序认证/授权服务。
*
* @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
方法。此方法允许您使用单个闭包快速定义认证过程。
要开始,请在 AuthServiceProvider
的 boot
方法中调用 Auth::viaRequest
方法。viaRequest
方法接受一个守卫名称作为其第一个参数。此名称可以是描述自定义守卫的任何字符串。传递给方法的第二个参数应是一个闭包,该闭包接收传入的 HTTP 请求并返回一个用户实例,或者如果认证失败,则返回 null
:
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
配置中使用此守卫:
'guards' => [
'api' => [
'driver' => 'custom-token',
],
],
添加自定义用户提供者
如果您不使用传统的关系数据库来存储用户,您将需要使用自己的认证用户提供者扩展 Laravel。我们将使用 Auth
facade 上的 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
{
/**
* 注册任何应用程序认证/授权服务。
*
* @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
函数通常接收一个表示用户的键,例如来自 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']
的值进行比较。此方法应返回 true
或 false
,指示密码是否有效。
可认证契约
现在我们已经探讨了 UserProvider
上的每个方法,让我们看看 Authenticatable
契约。请记住,提供者应从 retrieveById
、retrieveByToken
和 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
应返回用户的哈希密码。此接口允许认证系统与任何用户类一起工作,而不管您使用什么 ORM 或存储抽象层。默认情况下,Laravel 在 app
目录中包含一个实现此接口的 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\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],
'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],
'Illuminate\Auth\Events\PasswordReset' => [
'App\Listeners\LogPasswordReset',
],
];