跟踪laravel自带密码重置的源代码

最近使用laravel比较多,所以也钻研了部分的源代码。本文作为笔记已备日后查询。

首先从路由开始,laravel自带认证的密码重置控制器路由在Illuminate\Routing\Router.php中。

 /**
     * Register the typical authentication routes for an application.
     *
     * @return void
     */
    public function auth()
    {
        // Authentication Routes...
        $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
        $this->post('login', 'Auth\LoginController@login');
        $this->post('logout', 'Auth\LoginController@logout')->name('logout');

        // Registration Routes...
        $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
        $this->post('register', 'Auth\RegisterController@register');

        // Password Reset Routes...
        $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm');
        $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
        $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
        $this->post('password/reset', 'Auth\ResetPasswordController@reset');
    }

get('password/reset','Auth\ForgotPasswordController@showLinkRequestForm');
这个路由是用来返回密码重置申请页面的。这个很简单,不再多说。

我们看到这个路由
post('password/email','Auth\ForgotPasswordController@sendResetLinkEmail');
这是用来发送邮件的,我们顺着这一条追踪下去。

app\Http\Controllers\Auth\ForgotPasswordController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;

class ForgotPasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Password Reset Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password reset emails and
    | includes a trait which assists in sending these notifications from
    | your application to your users. Feel free to explore this trait.
    |
    */

    use SendsPasswordResetEmails;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }
}

可以看到控制器里代码很少,具体实现被封装在了SendsPasswordResetEmailstrait里。从代码上面的引用可以轻松地通过命名空间找到
Illuminate\Foundation\Auth\SendsPasswordResetEmails.php

 /**
     * Send a reset link to the given user.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function sendResetLinkEmail(Request $request)
    {
        $this->validate($request, ['email' => 'required|email']);

        // We will send the password reset link to this user. Once we have attempted
        // to send the link, we will examine the response then see the message we
        // need to show to the user. Finally, we'll send out a proper response.
        $response = $this->broker()->sendResetLink(
            $request->only('email')
        );

        return $response == Password::RESET_LINK_SENT
                    ? $this->sendResetLinkResponse($response)
                    : $this->sendResetLinkFailedResponse($request, $response);
    }

看这个方法,首先验证请求的email是否合法,然后

$response = $this->broker()->sendResetLink(
            $request->only('email')
        );

首先调用了自身的broker()方法,我们来看一下

  /**
     * Get the broker to be used during password reset.
     *
     * @return \Illuminate\Contracts\Auth\PasswordBroker
     */
    public function broker()
    {
        return Password::broker();
    }

看注释,发现返回的实例类型是 Contracts下面的,Contracts下面定义的全部是接口,我们看不到内部的实现,这下线索就断掉了。没关系,我们灵活一点,这里只要知道返回的是一个PasswordBroker接口的对象就行了。然后我们走捷径,直接用编辑器的搜索功能搜sendResetLink这个方法就行了。因为我们目标是看这封邮件是怎么发出去的。很容易就找到了这个方法的实现在
Illuminate\Auth\Passwords\PasswordBroker.php


    /**
     * Send a password reset link to a user.
     *
     * @param  array  $credentials
     * @return string
     */
    public function sendResetLink(array $credentials)
    {
        // First we will check to see if we found a user at the given credentials and
        // if we did not we will redirect back to this current URI with a piece of
        // "flash" data in the session to indicate to the developers the errors.
        $user = $this->getUser($credentials);

        if (is_null($user)) {
            return static::INVALID_USER;
        }

        // Once we have the reset token, we are ready to send the message out to this
        // user with a link to reset their password. We will then redirect back to
        // the current URI having nothing set in the session to indicate errors.
        $user->sendPasswordResetNotification(
            $this->tokens->create($user)
        );

        return static::RESET_LINK_SENT;
    }

首先从传入的参数中获得user,验证user是否存在,然后重点来了

 $user->sendPasswordResetNotification(
            $this->tokens->create($user)
        );

我们先不急着找sendPasswordResetNotification,我们先看一下$this->tokens->create($user),在类的构造方法中

 public function __construct(TokenRepositoryInterface $tokens,
                                UserProvider $users)
    {
        $this->users = $users;
        $this->tokens = $tokens;
    }

我们看到传入了一个TokenRepositoryInterface接口的实例,从字面上我们能判断出这个接口是和token生成有关的。我们直接搜索这个接口的实现。
Illuminate\Auth\Passwords\DatabaseTokenRepository.php这个类实现了TokenRepositoryInterface接口。我们看看create方法是怎样定义的。

  public function create(CanResetPasswordContract $user)
    {
        $email = $user->getEmailForPasswordReset();

        $this->deleteExisting($user);

        // We will create a new, random token for the user so that we can e-mail them
        // a safe link to the password reset form. Then we will insert a record in
        // the database so that we can verify the token within the actual reset.
        $token = $this->createNewToken();

        $this->getTable()->insert($this->getPayload($email, $token));

        return $token;
    }

果然这里面是生成了用于重置密码的token,可以我们发现两个奇怪的地方
1.参数传入的是一个CanResetPasswordContract接口实例,这个实例竟然就是user,那么我们要看看这个接口和User模型之间有什么关系。从该文件的引用我们知道CanResetPasswordContract是一个别名,真名叫Illuminate\Contracts\Auth\CanResetPassword但是我们不去找这个接口,因为明知道是接口,找过去一定扑个空。我们要找实现这个接口的位置,于是再次搜索。这下我们发现了Illuminate\Foundation\Auth\User.php

<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;
}

原来user模型实现了这个接口。第一个疑点解决。下面我们来看create方法中的第二个疑点。

2.这句话
$this->getTable()->insert($this->getPayload($email, $token));我们很容易可以知道是用来向password_reset表中写入数据的。但是我们没有看到它指定任何模型或者表名,那么它是在哪里找到这个表的呢。我们先看getTable()方法,这个方法就定义在下方,

/**
     * Begin a new database query against the table.
     *
     * @return \Illuminate\Database\Query\Builder
     */
    protected function getTable()
    {
        return $this->connection->table($this->table);
    }

    /**
     * Get the database connection instance.
     *
     * @return \Illuminate\Database\ConnectionInterface
     */
    public function getConnection()
    {
        return $this->connection;
    }

我特意把getConnection()方法一起粘过来。我们发现这两个方法都返回了该类的属性,那么我们就去构造方法中找传入的依赖。

  /**
     * Create a new token repository instance.
     *
     * @param  \Illuminate\Database\ConnectionInterface  $connection
     * @param  string  $table
     * @param  string  $hashKey
     * @param  int  $expires
     * @return void
     */
    public function __construct(ConnectionInterface $connection, $table, $hashKey, $expires = 60)
    {
        $this->table = $table;
        $this->hashKey = $hashKey;
        $this->expires = $expires * 60;
        $this->connection = $connection;
    }

构造方法中果然看到传入了一个table和一个connectionconnection我们不管,是数据库连接实例,我们找table,可是我们发现这里table就是一个简单的string类型,那么我们必须找到这个DatabaseTokenRepository类实例化的地方,继续搜索。我们找到了一个PasswordBrokerManager类里面createTokenRepository方法返回了DatabaseTokenRepository的实例对象。

/**
     * Create a token repository instance based on the given configuration.
     *
     * @param  array  $config
     * @return \Illuminate\Auth\Passwords\TokenRepositoryInterface
     */
    protected function createTokenRepository(array $config)
    {
        $key = $this->app['config']['app.key'];

        if (Str::startsWith($key, 'base64:')) {
            $key = base64_decode(substr($key, 7));
        }

        $connection = isset($config['connection']) ? $config['connection'] : null;

        return new DatabaseTokenRepository(
            $this->app['db']->connection($connection),
            $config['table'],
            $key,
            $config['expire']
        );
    }

我们发现这里传入了config数组里的table
我们在config\auth.php中找到了配置项

 'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

为什么实例对象要在这里返回呢,到这里我推测再搜索PasswordBrokerManager被引用的地方一定是一个服务提供者了。果然我们找到了Illuminate\Auth\Passwords\PasswordResetServiceProvider.php
服务容器通过这个服务提供者调用PasswordBrokerManager从而解析出DatabaseTokenRepository实例,提供依赖注入。

好现在我们再回到

 $user->sendPasswordResetNotification(
            $this->tokens->create($user)
        );

我们要来看sendPasswordResetNotification,直接搜索到定义处Illuminate\Auth\Passwords\CanResetPassword.php

   /**
     * Send the password reset notification.
     *
     * @param  string  $token
     * @return void
     */
    public function sendPasswordResetNotification($token)
    {
        $this->notify(new ResetPasswordNotification($token));
    }

我们追踪notify方法,notifyIlluminate\Notifications\RoutesNotifications.php的traitRoutesNotifications中定义。然后Illuminate\Notifications\Notifiable.phptrait中引用了RoutesNotifications,最后在app\User.php中引用了Notifiable。上面说到过Illuminate\Foundation\Auth\User.php实现了CanResetPassword接口,app\User.php中的User其实就是继承了Illuminate\Foundation\Auth\User.php

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
     ... ...
}

所以上面的

$this->notify(new ResetPasswordNotification($token));

中的this就是User实例,所以能够调用notify方法,这里继承实现关系比较复杂,需要自己多钻研。剩下就不再挖掘了,这里是通过laravel框架的通知来发送重置密码的邮件。

篇幅原因只能写到这里,继续深挖下去是无穷无尽的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 先说几句废话,调和气氛。事情的起由来自客户需求频繁变更,伟大的师傅决定横刀立马的改革使用新的框架(created ...
    wsdadan阅读 3,033评论 0 12
  • 简介 laravel 使实施认证的变得非常简单,事实上,它提供了非常全面的配置项以适应应用的业务。认证的配置文件存...
    Dearmadman阅读 6,107评论 2 13
  • 过去做事情急,什么东西拿起来就用,不喜欢进行系统性的学习,造成在使用过程中的错误和低效,现在感觉自己耐心多了,用之...
    马文Marvin阅读 1,964评论 0 10
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 2016-08-17 补充 Exception 部分改造方案的内容2016-08-13 补充 View 部分改造方...
    haolisand阅读 5,282评论 0 16