认证
介绍
想快速开始吗? 只需在一个新的Laravel应用中运行php artisan make:auth
和php 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 artisan make:auth
此命令应在新应用程序上使用,将安装一个布局视图、注册和登录视图,以及所有认证端点的路由。还将生成一个HomeController
来处理登录后请求到应用程序仪表板的请求。
如果您的应用程序不需要注册,您可以通过删除新创建的RegisterController
并修改路由声明来禁用它:Auth::routes(['register' => false]);
。
视图
如前一节所述,php artisan make:auth
命令将创建您需要的所有认证视图,并将其放置在resources/views/auth
目录中。
make:auth
命令还将创建一个resources/views/layouts
目录,其中包含应用程序的基本布局。所有这些视图都使用Bootstrap CSS框架,但您可以根据需要自由定制。
认证
现在您已经为包含的认证控制器设置了路由和视图,您可以为应用程序注册和认证新用户了!您可以在浏览器中访问您的应用程序,因为认证控制器已经包含了认证现有用户和将新用户存储到数据库的逻辑(通过其traits)。
路径定制
当用户成功认证后,他们将被重定向到/home
URI。您可以通过在LoginController
、RegisterController
、ResetPasswordController
和VerificationController
上定义一个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
中间件检测到未授权用户时,它将用户重定向到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内置的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,以防预期的目的地不可用。
指定附加条件
如果您愿意,您还可以在认证查询中添加额外的条件,除了用户的电子邮件和密码。例如,我们可以验证用户是否标记为“active”:
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
,控制器使用的traits已经实现了“记住”用户的正确逻辑。
如果您正在“记住”用户,您可以使用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
方法将用户登录到应用程序中以进行单个请求。不会使用会话或cookie,这意味着此方法在构建无状态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',
],
];