用户认证

介绍

Laravel 让用户认证变得非常简单。几乎所有的认证行为都可以通过配置信息 config/auth.php 来控制。

数据库注意事项

默认的 Laravel 在 app 文件夹中会含有 App\User Eloquent 模型。这个模型将使用默认的 Eloquent 认证来驱动。如果你的应用程序没有使用 Eloquent,请选择使用 Laravel 查询构造器的 database 认证驱动。

App\User 模型创建数据库表结构时,确认密码字段最少必须 60 字符长。

users 数据表中必须含有 nullable 、100 字符长的 remember_token 字段,这个字段将会被用来保存「记住我」 session 的令牌。只要在创建迁移时,使用 $table->rememberToken(),即可轻松加入这个字段。

认证快速入门

Laravel 带有两个认证控制器,它们被放置在 App\Http\Controllers\Auth 命名空间内,AuthController 处理用户注册及认证,而 PasswordController 负责处理重置用户的密码。

这些控制器使用了 trait 来包含所需要的方法,对于大多数的应用程序而言,你并不需要修改这些控制器。

路由

默认没有 路由 指向这些认证控制器,需要自己添加到 app/Http/routes.php 中。

  1. // 认证路由...
  2. Route::get('auth/login', 'Auth\AuthController@getLogin');
  3. Route::post('auth/login', 'Auth\AuthController@postLogin');
  4. Route::get('auth/logout', 'Auth\AuthController@getLogout');
  5. // 注册路由...
  6. Route::get('auth/register', 'Auth\AuthController@getRegister');
  7. Route::post('auth/register', 'Auth\AuthController@postRegister');

视图

虽然这些认证控制器被包含在框架中,你仍需提供 视图 给控制器渲染,而视图需要被放置在 resources/views/auth 文件夹中,你可以任意的自定义这些视图。

登录视图应该被放在 resources/views/auth/login.blade.php 而注册视图则放在 resources/views/auth/register.blade.php

认证表单例子

  1. <!-- resources/views/auth/login.blade.php -->
  2. <form method="POST" action="/auth/login">
  3. {!! csrf_field() !!}
  4. <div>
  5. Email
  6. <input type="email" name="email" value="{{ old('email') }}">
  7. </div>
  8. <div>
  9. Password
  10. <input type="password" name="password" id="password">
  11. </div>
  12. <div>
  13. <input type="checkbox" name="remember"> Remember Me
  14. </div>
  15. <div>
  16. <button type="submit">Login</button>
  17. </div>
  18. </form>

注册表单例子

  1. <!-- resources/views/auth/register.blade.php -->
  2. <form method="POST" action="/auth/register">
  3. {!! csrf_field() !!}
  4. <div>
  5. Name
  6. <input type="text" name="name" value="{{ old('name') }}">
  7. </div>
  8. <div>
  9. Email
  10. <input type="email" name="email" value="{{ old('email') }}">
  11. </div>
  12. <div>
  13. Password
  14. <input type="password" name="password">
  15. </div>
  16. <div>
  17. Confirm Password
  18. <input type="password" name="password_confirmation">
  19. </div>
  20. <div>
  21. <button type="submit">Register</button>
  22. </div>
  23. </form>

认证

现在你已经为认证控制器设置好了路由及视图,你可以在应用程序注册并认证新用户了。

只需要在浏览器简单地访问定义的路由就行了,认证控制器早已包含了处理认证现有用户,及保存新用户在数据库的逻辑了(通过他们各自的 traits)。

当用户成功认证后,他们将被重定向到 /home URI,而你需要向路由注册此 URI 来处理这个请求,也可以自定义认证后转向的 URI,只需修改 AuthControllerredirectPath 属性即可:

  1. protected $redirectPath = '/dashboard';

用户认证失败后,将会被重定向到 /auth/login URI。你可以设置 AuthControllerloginPath 属性来自定义认证失败后的重定向位置:

  1. protected $loginPath = '/login';

loginPath 并不会改变当用户访问受保护的路由时所重定向的路径。该路径是由 App\Http\Middleware\Authenticate 中间件的 handle 方法所控制的。

自定义表单字段

如果想要修改注册时的表单字段,或是自定义如何将新用户的记录写入数据库,你可以修改 AuthController 类,这个类负责验证和创建新的用户。

AuthControllervalidator 方法包含了对新用户的验证规则。你可以随意的修改这个方法。

AuthControllercreate 方法负责使用 Eloquent ORM 来创建新的 App\User 纪录到数据库。你可以根据需求任意修改这个方法。

获取已认证的用户信息

可以通过 Auth facade 来访问认证的用户。

  1. $user = Auth::user();

也有另外一种方法可以访问认证过的用户,就是通过 Illuminate\Http\Request 实例:

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Routing\Controller;
  5. class ProfileController extends Controller
  6. {
  7. /**
  8. * 更新用户的数据
  9. *
  10. * @param Request $request
  11. * @return Response
  12. */
  13. public function updateProfile(Request $request)
  14. {
  15. if ($request->user()) {
  16. // $request->user() 返回认证过的用户的实例...
  17. }
  18. }
  19. }

检查用户是否登录

使用 Auth facade 的 check 方法来检查用户是否登录,如果已经登录,将会返回 true

  1. if (Auth::check()) {
  2. // 这个用户已经登录...
  3. }

在允许该用户访问特定的路由或控制器之前,可以使用中间件来检查用户是否认证过。要想得到更多信息,请阅读 限制路由访问 的文档。

限制路由访问

路由中间件 用于限定认证过的用户访问指定的路由,Laravel 提供了 auth 中间件来达到这个目的,而这个中间件被定义在 app\Http\Middleware\Authenticate.php 中,只需要将它应用到路由定义中:

  1. // 使用路由闭包...
  2. Route::get('profile', ['middleware' => 'auth', function() {
  3. // 只有认证过的用户能进来这里...
  4. }]);
  5. // 使用控制器...
  6. Route::get('profile', [
  7. 'middleware' => 'auth',
  8. 'uses' => 'ProfileController@show'
  9. ]);

如果使用 控制器类,可以在构造器中调用 middleware 方法:

  1. public function __construct()
  2. {
  3. // 执行 auth 认证
  4. $this->middleware('auth');
  5. }

错误尝试限制

Laravel 内置的 AuthController 类提供 Illuminate\Foundation\Auth\ThrottlesLogins trait 允许你在应用程序中限制登录次数。

默认情况下,如果用户在进行几次尝试后仍不能提供正确的凭证,将在一分钟内无法进行登录。这个限制会特别针对用户的用户名称 / 邮件地址和他们的 IP 地址:

  1. <?php
  2. namespace App\Http\Controllers\Auth;
  3. use App\User;
  4. use Validator;
  5. use App\Http\Controllers\Controller;
  6. use Illuminate\Foundation\Auth\ThrottlesLogins;
  7. use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
  8. class AuthController extends Controller
  9. {
  10. use AuthenticatesAndRegistersUsers, ThrottlesLogins;
  11. // 重置 AuthController 类...
  12. }

手动认证用户

当然,不一定要使用 Laravel 内置的认证控制器,你可以选择删除这些控制器,然后创建自定义的控制器。

我们可以利用 Auth facade 来访问 Laravel 的认证服务,从而实现手动认证。

接下来让我们看一下 Authattempt 方法:

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Auth;
  4. use Illuminate\Routing\Controller;
  5. class AuthController extends Controller
  6. {
  7. /**
  8. * 处理认证
  9. *
  10. * @return Response
  11. */
  12. public function authenticate()
  13. {
  14. // 尝试登录
  15. if (Auth::attempt(['email' => $email, 'password' => $password])) {
  16. // 认证通过...
  17. return redirect()->intended('dashboard');
  18. }
  19. }
  20. }

attempt 方法会接受一个数组来作为第一个参数,这个数组的值可用来寻找数据库里的用户数据,所以在上面的例子中,用户通过 email 字段被取出,如果用户被找到了,数据库里经过哈希的密码将会与数组中哈希的 password 值比对,如果两个值一样的话就会开启一个通过认证的 session 给用户。

如果认证成功,attempt 方法将会返回 true,反之则为 false

重定向器上的 intended 方法将会重定向用户回原本想要进入的页面,也可以传入一个回退 URI 至这个方法,以避免要转回的页面不可使用。

可以加入除用户的邮箱及密码外的额外条件进行认证查找。例如,我们要确认用户是否被标示为 active

  1. if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
  2. // 此用户是已激活的、没被停权的、且存在的用户
  3. }

要想让用户注销,你可以使用 Auth facade 的 logout 方法。这个方法会清除所有认证后加入到用户 session 的数据:

  1. Auth::logout();

注意: 在这些例子中,email 不是一个一定要有的选项,它仅仅是被用来当作例子,你可以用任何字段,如「手机号码」,只要它在数据库的意义等同于「用户名」。

记住用户

如果你想要提供「记住我」的功能,你需要传入一个布尔值到 attempt 方法的第二个参数,在用户注销前 session 值都会被一直保存。

users 数据表一定要包含一个 remember_token 字段,这是用来保存「记住我」令牌的。

  1. if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
  2. // 这个用户被记住了...
  3. }

可以使用 viaRemember 方法来检查这个用户是否使用「记住我」 cookie 来做认证:

  1. if (Auth::viaRemember()) {
  2. //
  3. }

其它认证方法

用「用户实例」做认证

如果你需要使用存在的用户实例来登录,你需要调用 login 方法,并传入使用实例,这个对象必须是由 Illuminate\Contracts\Auth\Authenticatable contract 所实现。当然,App/User 模型已经实现了这个接口:

  1. Auth::login($user);

用用户 ID 做认证

使用 loginUsingId 方法来登录指定 ID 用户,这个方法接受要登录用户的主键:

  1. Auth::loginUsingId(1);

仅在本次认证用户

可以使用 once 方法来针对一次性认证用户,没有任何的 session 或 cookie 会被使用,这个对于构建无状态的 API 非常的有用,once 方法跟 attempt 方法拥有同样的传入参数:

  1. if (Auth::once($credentials)) {
  2. //
  3. }

HTTP 基础认证

HTTP 基础认证 提供一个快速的方法来认证用户,不需要任何「登录」页面。开始之前,先增加 auth.basic 中间件 到你的路由,auth.basic 中间件已经被包含在 Laravel 框架中,所以你不需要定义它:

  1. Route::get('profile', ['middleware' => 'auth.basic', function() {
  2. // 只有认证过的用户可进入...
  3. }]);

一旦中间件被增加到路由上,当使用浏览器进入这个路由时,将自动的被提示需要提供凭证。默认情况下,auth.basic 中间件将会使用用户的 email 字段当作「用户名」。

FastCGI 的注意事项

如果是正在使用 FastCGI,则 HTTP 的基础认证可能无法正常运作,你需要将下面这几行加入你 .htaccess 文件中:

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

无状态 HTTP 基础认证

你可以使用 HTTP 基础认证而不用在 session 中设置用户认证用的 cookie,这个功能对 API 认证来说非常有用。为了达到这个目的,定义一个中间件 并调用 onceBasic 方法。如果从 onceBasic 方法没有返回任何响应的话,这个请求会直接传进应用程序中:

  1. <?php
  2. namespace Illuminate\Auth\Middleware;
  3. use Auth;
  4. use Closure;
  5. class AuthenticateOnceWithBasicAuth
  6. {
  7. /**
  8. * 处理请求
  9. *
  10. * @param \Illuminate\Http\Request $request
  11. * @param \Closure $next
  12. * @return mixed
  13. */
  14. public function handle($request, Closure $next)
  15. {
  16. return Auth::onceBasic() ?: $next($request);
  17. }
  18. }

接着,注册这个路由中间件,然后将它增加在一个路由上:

  1. Route::get('api/user', ['middleware' => 'auth.basic.once', function() {
  2. // 只有认证过的用户可以进入...
  3. }]);

重设密码

重设数据库

很多 Web 应用程序都会提供用户重设密码功能,Laravel 提供了发送重置密码邮件和实现密码重设功能,避免让你重造车轮。

开始之前,请先确认 App\User 模型实现了 Illuminate\Contracts\Auth\CanResetPassword contract。当然,原有的 App\User 早已实现了这个接口,并且使用 Illuminate\Auth\Passwords\CanResetPassword trait 引入实现这个接口所需要的方法。

生成重置令牌的数据表迁移文件

接下来,必须要创建一个用来保存密码重置令牌的数据表,而这个数据表的迁移已经包含在 Laravel 中了,就在 database/migrations 文件夹里。所以,你要做的就是做一次迁移:

  1. php artisan migrate

路由

Laravel 包含了 Auth\PasswordController,虽然它含有所有重置用户密码的逻辑,但是你仍需要指定路由到这个控制器上:

  1. // 密码重置链接的路由...
  2. Route::get('password/email', 'Auth\PasswordController@getEmail');
  3. Route::post('password/email', 'Auth\PasswordController@postEmail');
  4. // 密码重置的路由...
  5. Route::get('password/reset/{token}', 'Auth\PasswordController@getReset');
  6. Route::post('password/reset', 'Auth\PasswordController@postReset');

视图

除了定义 PasswordController 路由,还需要提供视图给这个控制器。不过不用担心,我们已经提供了样例视图来帮助你开始,你也可以随意的定义表单风格。

重置密码链接的请求表单例子

你需要提供 HTML 视图给密码重置的请求表单。这些视图被放在 resources/views/auth/password.blade.php。这个表单提供了单个的字段来给用户输入邮箱,让他们可以收到密码重置链接:

  1. <!-- resources/views/auth/password.blade.php -->
  2. <form method="POST" action="/password/email">
  3. {!! csrf_field() !!}
  4. @if (count($errors) > 0)
  5. <ul>
  6. @foreach ($errors->all() as $error)
  7. <li>{{ $error }}</li>
  8. @endforeach
  9. </ul>
  10. @endif
  11. <div>
  12. Email
  13. <input type="email" name="email" value="{{ old('email') }}">
  14. </div>
  15. <div>
  16. <button type="submit">
  17. 发送重置密码邮件
  18. </button>
  19. </div>
  20. </form>

当用户送出重置密码的请求,他们会收到一封有链接到 PasswordControllergetReset 方法(通常是路由到 /password/reset)的邮件。

你将需要为这份邮件创造一个 resources/views/emails/password.blade.php 视图。这个视图会接收一个带有密码重置令牌的 $token 变量,用来拼接成为重置密码的 URL,这个 $token 变量含有密码重置令牌来匹配用户的密码重置请求,举例如下:

<!-- 文件 resources/views/emails/password.blade.php -->

点击此处重置你的密码:{{ url('password/reset/'.$token) }}

密码重置表单的例子

当用户点击了邮箱的链接来重置密码,将会显示一个密码重置表单,这个视图被放在 resources/views/auth/reset.blade.php

这里有个密码重置表单的例子:

<!-- resources/views/auth/reset.blade.php -->

<form method="POST" action="/password/reset">
    {!! csrf_field() !!}
    <input type="hidden" name="token" value="{{ $token }}">

    @if (count($errors) > 0)
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    @endif

    <div>
        电子邮箱
        <input type="email" name="email" value="{{ old('email') }}">
    </div>

    <div>
        密码
        <input type="password" name="password">
    </div>

    <div>
        重新输入密码
        <input type="password" name="password_confirmation">
    </div>

    <div>
        <button type="submit">
            重置密码
        </button>
    </div>
</form>

重设密码后

在你定义好路由跟视图之后,你就可以使用浏览器访问这个路由了。

PasswordController 已经包含了发送密码重置链接邮箱,及更新密码到数据库的逻辑。

密码重置以后,这个用户会自动登录并重定向到 /home。要自定义重定向地址,只需定义 PasswordControllerredirectTo 属性即可:

protected $redirectTo = '/dashboard';

注意: 默认情况下,密码重置令牌会在一个小时后过期,你可以更改 config/auth.phpreminder.expire 选项,来修改这个设置。

社会化认证

除了传统的表单认证之外,Laravel 同样提供了简单方便的方法来认证 OAuth 提供者,这个方法使用了 Laravel Socialite。Socialite 目前支持 Facebook、Twitter、LinkedIn、Google、GitHub 跟 Bitbucket 的认证。

开始使用 Socialite 前,请先添加依赖包至你的 composer.json

composer require laravel/socialite

配置信息

安装 Socialite 之后,到 config/app.php 配置文件中注册 Laravel\Socialite\SocialiteServiceProvider

'providers' => [
    // 其它服务提供者...

    Laravel\Socialite\SocialiteServiceProvider::class,
],

此外再添加 Socialite facade 到 app 配置文件的 aliases 数组中:

'Socialite' => Laravel\Socialite\Facades\Socialite::class,

你将需要添加凭证来使用 OAuth 服务,这些凭证需要被放在 config/services.php 配置文件中,并根据你应用程序的需求,增加 facebooktwitterlinkedingooglegithubbitbucket 的键,例如:

'github' => [
    'client_id' => 'your-github-app-id',
    'client_secret' => 'your-github-app-secret',
    'redirect' => 'http://your-callback-url',
],

基础应用

接下来,你已经准备好开始认证用户了!需要两个路由: 一个用来重定向用户到 OAuth 提供者,另一个在认证后接收提供者的回调。我们将会借由 Socialite facade 访问 Socialite:

<?php

namespace App\Http\Controllers;

use Socialite;
use Illuminate\Routing\Controller;

class AuthController extends Controller
{
    /**
     * 重定向用户到 GitHub 认证页。
     *
     * @return Response
     */
    public function redirectToProvider()
    {
        return Socialite::driver('github')->redirect();
    }

    /**
     * 从 GitHub 得到用户信息
     *
     * @return Response
     */
    public function handleProviderCallback()
    {
        $user = Socialite::driver('github')->user();

        // $user->token;
    }
}

redirect 方法会负责处理并发送用户到 OAuth 提供者,而 user 方法会从提供者返回的请求来获取用户信息。在重定向用户之前,你也可以使用 scopes 方法来设置请求的 「作用域」。这个方法将重写所有已经存在的作用域:

return Socialite::driver('github')
            ->scopes(['scope1', 'scope2'])->redirect();

当然,你需要指定路由到控制器方法:

Route::get('auth/github', 'Auth\AuthController@redirectToProvider');
Route::get('auth/github/callback', 'Auth\AuthController@handleProviderCallback');

一些 OAuth 提供者支持在重定向的请求中自定义参数。若要在请求中加入任何自定义参数,只要调用 with 方法并带上一个关联数组:

return Socialite::driver('google')
            ->with(['hd' => 'example.com'])->redirect();

获取用户详情信息

一旦你有了用户的实例,你就可以获取用户更详细的信息:

$user = Socialite::driver('github')->user();

// OAuth Two 提供者
$token = $user->token;

// OAuth One 提供者
$token = $user->token;
$tokenSecret = $user->tokenSecret;

// 所有提供者
$user->getId();
$user->getNickname();
$user->getName();
$user->getEmail();
$user->getAvatar();

添加自定义的认证驱动

如果你不是使用传统的关系型数据库来保存用户,你将需要扩充 Laravel 来添加你自己的认证驱动。我们将使用 Auth facade 的 extend 方法来定义自定义驱动。你应该将 extend 放置在 服务提供者 中:

<?php

namespace App\Providers;

use Auth;
use App\Extensions\RiakUserProvider;
use Illuminate\Support\ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 运行服务注册后的启动进程。
     *
     * @return void
     */
    public function boot()
    {
        Auth::extend('riak', function($app) {
            // 返回 Illuminate\Contracts\Auth\UserProvider 的实例...
            return new RiakUserProvider($app['riak.connection']);
        });
    }

    /**
     * 在容器注册绑定。
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

在你用 extend 方法注册这个驱动后,你可以在 config/auth.php 转换到新的驱动。

用户提供者 Contract

Illuminate\Contracts\Auth\UserProvider 的实现只负责获取 Illuminate\Contracts\Auth\Authenticatable 的实现, 且不受限于永久保存系统,例如 MySQL, Riak 等等。这两个接口允许 Laravel 认证机制继续作用,而不用管用户如何保存或是使用什么样类型的类实现它。

让我们来看看 Illuminate\Contracts\Auth\UserProvider contract:

<?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。Authenticatable 的实现通过 ID 匹配的方法来取出和返回。

retrieveByToken 函数借助用户唯一的 $identifier 和「记住我」$token 来获取用户。如同之前的方法,Authenticatable 的实现应该被返回。

updateRememberToken 方法使用新的 $token 更新了 $userremember_token 字段。这个新的令牌可以是全新的令牌(当使用「记住我」尝试登录成功时),或是 null(当用户注销时)。

retrieveByCredentials 方法获取了从 Auth::attempt 方法发送过来的凭证数组(当想要登录时)。这个方法应该要 「查找」所使用的持久化存储系统来匹配这些凭证。通常,这个方法会运行一个带着「where」$credentials['username'] 条件的查找。这个方法接着需要返回一个 UserInterface 的实现。此方法不应该企图做任何密码验证或认证操作。

validateCredentials 方法需要比较 $user$credentials 来认证这个用户。例如,这个方法可能会比较 $user->getAuthPassword() 字符串及 Hash::make 后的 $credentials['password']。这个方法应该只验证用户的凭证并返回一个布尔值。

可验证之 Contract

现在我们已经介绍了 UserProvider 的每个方法,让我们看一下 Authenticate contract。这个提供者需要 retrieveByIdretrieveByCredentials 方法来返回这个接口的实现:

<?php

namespace Illuminate\Contracts\Auth;

interface Authenticatable {

    public function getAuthIdentifier();
    public function getAuthPassword();
    public function getRememberToken();
    public function setRememberToken($value);
    public function getRememberTokenName();

}

这个接口很简单。getAuthIdentifier 方法需要返回用户的「主键」。在 MySQL 中,这个主键是指自动增加的主键。而 getAuthPassword 应该要返回用户哈希后的密码。这个接口允许认证系统和任何用户类运作,不用管你在使用何种 ORM 或存储抽象层。默认情况下,Laravel 的 app 文件夹中会包含 User 类来实现此接口,所以你可以观察这个类以作为实现的例子。

事件

Laravel 提供了在认证过程中的各种 事件。你可以在 EventServiceProvider 中对这些事件做监听:

/**
 * 为你的应用程序注册任何事件。
 *
 * @param  \Illuminate\Contracts\Events\Dispatcher  $events
 * @return void
 */
public function boot(DispatcherContract $events)
{
    parent::boot($events);

    // 在每次尝试认证时触发...
    $events->listen('auth.attempt', function ($credentials, $remember, $login) {
        //
    });

    // 登录成功时触发...
    $events->listen('auth.login', function ($user, $remember) {
        //
    });

    // 注销时触发...
    $events->listen('auth.logout', function ($user) {
        //
    });
}

{note} 欢迎任何形式的转载,但请务必注明出处,尊重他人劳动共创开源社区。

转载请注明:本文档由 Laravel China 社区 [laravel-china.org] 组织翻译。

文档永久地址: http://d.laravel-china.org