# 用户认证
- [介绍](#introduction)
- [数据库注意事项](#introduction-database-considerations)
- [用户认证快速指南](#authentication-quickstart)
- [路由](#included-routing)
- [视图](#included-views)
- [认证](#included-authenticating)
- [检索已验证的用户](#retrieving-the-authenticated-user)
- [路由保护](#protecting-routes)
- [密码确认](#password-confirmation)
- [登录限制](#login-throttling)
- [手动验证用户](#authenticating-users)
- [记住用户](#remembering-users)
- [其他认证方法](#other-authentication-methods)
- [HTTP 基本认证](#http-basic-authentication)
- [无状态 HTTP 基本认证](#stateless-http-basic-authentication)
- [登出](#logging-out)
- [其他设备的会话无效](#invalidating-sessions-on-other-devices)
- [社会认证](https://github.com/laravel/socialite)
- [添加自定义保护](#adding-custom-guards)
- [闭包看守器](#closure-request-guards)
- [添加自定义用户提供程序](#adding-custom-user-providers)
- [用户提供程序合同](#the-user-provider-contract)
- [可认证的合同](#the-authenticatable-contract)
- [事件](#events)
## 介绍
> 提示: **想要快速开始吗?** Composer 安装 `laravel/ui` 包并在一个全新的 Laravel 应用程序中运行 `php artisan ui vue --auth` 。迁移数据库后,将浏览器导航到 `http://your-app.test/register` 或分配给应用程序的任何其他 URL。这两个命令将负责构建整个认证系统!
Laravel 使得实现身份验证非常简单。 事实上,几乎所有的配置都是现成的。 身份验证配置文件位于 `config/auth.php`, 其中包含几个有良好文档记录的选项,用于调整身份验证服务的行为。
在其核心,Laravel 的认证设施由「守卫」和「提供者」组成。守卫决定如何对每个请求的用户进行身份验证。比如,Laravel 带有一个 `session` 保护,它使用会话存储和 Cookies 来维护状态。
提供者决定如何从持久储存中检索用户。 Laravel 支持使用 Eloquent 和数据库查询生成器检索用户。但是,你可以根据应用程序的需要来自由定义其他提供者。
如果这些听起来让你有些疑惑,不要担心!许多应用程序永远不需要修改默认的身份验证配置。
### 数据库注意事项
默认情况下, Laravel 包含一个 `App\User` [Eloquent model](/docs/{{version}}/eloquent) 在你的 `app` 目录下。 这个模型可与默认的 Eloquent 身份验证驱动程序一起使用。如果你的应用程序没有使用 Eloquent,你可以用 `database` 身份验证驱动程序,它用的是 Laravel 查询生成器。
当为 `App\User` 模型生成数据库架构时,确保密码的长度至少为 60 个字符。保持默认的字符串长度为 255 个字符是一个不错的选择。.
另外,你应该验证 `users`(或等效)表是否包含一个可空的,含有 100 个字符的 `remember_token` 字符串。此列将用于存储用户登录应用程序时选择「记住我」选项的令牌。
## 用户认证快速指南
Laravel 附带了几个预构建的身份验证控制器,它们位于 `App\Http\Controllers\Auth` 命名空间中。`RegisterController` 处理新的用户注册,`LoginController` 处理身份验证,`ForgotPasswordController` 处理用于重置密码的电子邮件链接,`ResetPasswordController` 包含重置密码的逻辑。这些控制器中的每一个都使用一个特性来包含它们的必要方法。对于许多应用程序,你根本不需要修改这些控制器。
### 路由
Laravel的 `laravel/ui` 包提供了一种快速方法,可以使用一些简单的命令来支持你进行身份验证所需的所有路由和视图:
composer require laravel/ui "^1.0" --dev
php artisan ui vue --auth
这个命令应该在新应用程序上使用,并将安装布局视图,注册和登录视图以及所有身份验证端点的路由。 还将生成一个 `HomeController` 来处理登录后跳转到Home页面。
> 提示:如果应用程序不需要注册,可以通过删除新创建的 `RegisterController` 并修改路由声明来禁用它:`Auth::routes(['register' => false]);`。
### 视图
如上一节所述,`laravel/ui` 包的 `php artisan ui vue --auth` 命令将创建验证所需的所有视图,并将它们放在 `resources/views/auth` 目录下。
`ui` 命令还将创建一个 `resources/views/layouts` 目录,其中包含应用程序的基本布局。 这些视图都使用 Bootstrap CSS 框架,但是你可以自由地定制它们。
### 认证
现在你已经给身份验证控制器设置了路由和视图,你可以为应用程序注册和验证新用户了! 因为控制器已经默认包含了验证用户是否存在和保存用户到数据库中的认证逻辑(通过 traits 实现的),现在你已经可以在浏览器中访问应用了。
#### 自定义路径
当用户认证成功,他们会被重定向到 `/home` 这个 URI 下。你可以在`RouteServiceProvider`中修改常量`HOME`的来定义认证成功后的重定向路径:
public const HOME = '/home';
如果你想要更加灵活的自定义用户验证通过后的HTTP响应,你可以重写`trait AuthenticatesUsers`的`authenticated(Request $request, $user)`方法。
```php
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
return redirect('/');
}
```
#### 自定义用户名
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](/docs/{{version}}/eloquent) 在数据库中创建新的 `App\User` 记录。你可以根据数据库的需要自定义该方法。
### 检索认证用户
你可以通过 `Auth` facade 来访问已认证的用户:
use Illuminate\Support\Facades\Auth;
// 获取当前通过认证的用户...
$user = Auth::user();
// 获取当前通过认证的用户 ID...
$id = Auth::id();
或者,你可以通过 `Illuminate\Http\Request` 实例来访问已认证的用户。别忘了,类型提示的类会被自动注入到你的控制器方法中:
user() 返回一个认证用户实例...
}
}
#### 确定当前用户是否已经认证
你可以使用 `Auth` facade 的 `check` 方法来检查用户是否已认证。如果已认证,将会返回 `true`:
use Illuminate\Support\Facades\Auth;
if (Auth::check()) {
// 用户已经登录了...
}
> 提示:虽然可以使用 `check` 方法确认用户是否被认证,但是在允许用户访问的某些路由 / 控制器之前,通常还是会使用中间件来验证用户是否进行过身份验证。想要了解更多信息,请查看有关 [保护路由](/docs/{{version}}/authentication#protecting-routes) 的文档。
### 保护路由
[路由中间件](/docs/{{version}}/middleware) 可以用于只允许通过认证的用户访问给定的路由。Laravel 自带了一个 `auth` 中间件,它定义在 `Illuminate\Auth\Middleware\Authenticate` 中。由于这个中间件已经在 HTTP 内核中注册,你只需把这个中间件附加到路由定义中:
Route::get('profile', function () {
// 只有认证过的用户可以进入...
})->middleware('auth');
当然,如果你使用 [控制器](/docs/{{version}}/controllers),你可以在控制器的构造函数中调用 `middleware` 方法来直接将其附加到路由定义中:
public function __construct()
{
$this->middleware('auth');
}
#### 重定向未认证的用户
当 `auth` 中间件检测到一个未认证用户时,它会把用户重定向到名为 `login` 的 [命名路由](/docs/{{version}}/routing#named-routes)上。
您可以通过修改 `app/Http/Middleware/Authenticate.php` 文件中的 `redirectTo` 函数来修改此行为:
/**
* Get the path the user should be redirected to.
*
* @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`中间件关联到一个路由上,当用户访问该路由时就会自动跳转到一个密码确认页面。
```php
Route::get('/settings/security', function () {
// Users must confirm their password before continuing...
})->middleware(['auth', 'password.confirm']);
```
当用户成功完成密码确认后,将会被重定向到原先要访问的路由。默认情况下密码确认之后的3小时内再访问敏感页面是无需重复确认的。你可以通过`auth.password_timeout`配置项来修改这个有效时间。
### 登录限流
如果你使用 Laravel 内置的 `LoginController` 类, `Illuminate\Foundation\Auth\ThrottlesLogins` trait 已经包含在该控制器中了。 默认情况下,如果用户多次尝试却无法提供正确的登录凭据,那么该用户在一分钟内将不能再次尝试登录。这种限流策略基于用户的用户名 / 邮箱地址及其 IP 地址的唯一性。
## 手动验证用户
不一定非要在 Lavarel 中使用验证控制器。如果选择删除这些控制器,就需要直接使用 Lavarel 验证类。别担心,很容易!
可以借助 `Auth` [facade](https://learnku.com/docs/laravel/5.8/facades) 访问 Laravel 服务,因此需要在类的开头导入 `Auth` 。下面来看看 `attempt` 方法:
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)) {
// 用户已经被记住...
}
> Tip:如果使用了 Laravel 内置的 `LoginController`,「记住」用户的正确逻辑已经由控制器所用的 traits 实现。
如果启用了「记住用户」,可以使用 `viaRemember` 方法判断是否使用了「记住我」cookie 对用户做身份验证
if (Auth::viaRemember()) {
//
}
### 其它身份验证方法
#### 验证用户实例
如果要将已经存在的用户登入应用,可以调用 `login` 方法,并以用户实例作为其参数 。该对象必须实现 `Illuminate\Contracts\Auth\Authenticatable` [契约](/docs/{{version}}/contracts) 。Laravel 自带的 `App\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 基础认证](https://en.wikipedia.org/wiki/Basic_access_authentication) 提供了一种快速方法来验证你应用程序中的用户,而无需设置专用的「登录」页面。 开始之前, 先把 `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 的身份验证特别有用。为此 ,请 [定义一个中间件](/docs/{{version}}/middleware) 它将调用 `onceBasic` 方法。如果 `onceBasic` 方法没有返回任何响应,那么请求就可以进一步传递到应用程序中:
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);
> {note} 当调用`logoutOtherDevices`方法后,用户的其它 Session 将完全失效,这意味着他们将「退出」他们之前通过身份认证的所有看守器。
## 添加自定义的看守器
你可以使用 `Auth` facade 的 `extend` 方法来定义自己的身份验证看守器。你应该在 [服务提供器](/docs/{{version}}/providers) 中调用 `extend` 方法。由于 Laravel 已经附带了 `AuthServiceProvider`,我们可以将代码放在该提供器中:
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',
],
],
## 添加自定义用户提供器
如果不使用传统的关系数据库存储用户,就需要使用自己的身份验证用户提供器扩展 Lavarel。可以使用 `Auth` facade 的 `provider` 方法自定义用户提供器:
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` 契约:
getAuthPassword()` 的值与 `$credentials['password']` 的值。它应该返回 `true` 或 `false` ,以表明用户密码是否有效。
### 身份验证契约
我们已经剖析了 `UserProvider` 的每个方法。下面再来看看 `Authenticatable` 契约。切记,用户提供器的 `retrieveById`、 `retrieveByToken` 和 `retrieveByCredentials` 方法将返回此接口的实例:
## 事件
在身份验证处理过程中 Laravel 引发了多种 [事件](https://learnku.com/docs/laravel/6.0/events) 。 可以在 `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',
],
];