跟蹤laravel自帶密碼重置的源代碼

最近使用laravel比較多,所以也鉆研了部分的源代碼。本文作為筆記已備日后查詢。

首先從路由開始,laravel自帶認(rèn)證的密碼重置控制器路由在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');
這個(gè)路由是用來返回密碼重置申請(qǐng)頁面的。這個(gè)很簡單,不再多說。

我們看到這個(gè)路由
post('password/email','Auth\ForgotPasswordController@sendResetLinkEmail');
這是用來發(fā)送郵件的,我們順著這一條追蹤下去。

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');
    }
}

可以看到控制器里代碼很少,具體實(shí)現(xiàn)被封裝在了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);
    }

看這個(gè)方法,首先驗(yàn)證請(qǐng)求的email是否合法,然后

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

首先調(diào)用了自身的broker()方法,我們來看一下

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

看注釋,發(fā)現(xiàn)返回的實(shí)例類型是 Contracts下面的,Contracts下面定義的全部是接口,我們看不到內(nèi)部的實(shí)現(xiàn),這下線索就斷掉了。沒關(guān)系,我們靈活一點(diǎn),這里只要知道返回的是一個(gè)PasswordBroker接口的對(duì)象就行了。然后我們走捷徑,直接用編輯器的搜索功能搜sendResetLink這個(gè)方法就行了。因?yàn)槲覀兡繕?biāo)是看這封郵件是怎么發(fā)出去的。很容易就找到了這個(gè)方法的實(shí)現(xiàn)在
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;
    }

首先從傳入的參數(shù)中獲得user,驗(yàn)證user是否存在,然后重點(diǎn)來了

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

我們先不急著找sendPasswordResetNotification,我們先看一下$this->tokens->create($user),在類的構(gòu)造方法中

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

我們看到傳入了一個(gè)TokenRepositoryInterface接口的實(shí)例,從字面上我們能判斷出這個(gè)接口是和token生成有關(guān)的。我們直接搜索這個(gè)接口的實(shí)現(xiàn)。
Illuminate\Auth\Passwords\DatabaseTokenRepository.php這個(gè)類實(shí)現(xiàn)了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,可以我們發(fā)現(xiàn)兩個(gè)奇怪的地方
1.參數(shù)傳入的是一個(gè)CanResetPasswordContract接口實(shí)例,這個(gè)實(shí)例竟然就是user,那么我們要看看這個(gè)接口和User模型之間有什么關(guān)系。從該文件的引用我們知道CanResetPasswordContract是一個(gè)別名,真名叫Illuminate\Contracts\Auth\CanResetPassword但是我們不去找這個(gè)接口,因?yàn)槊髦朗墙涌冢疫^去一定撲個(gè)空。我們要找實(shí)現(xiàn)這個(gè)接口的位置,于是再次搜索。這下我們發(fā)現(xiàn)了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模型實(shí)現(xiàn)了這個(gè)接口。第一個(gè)疑點(diǎn)解決。下面我們來看create方法中的第二個(gè)疑點(diǎn)。

2.這句話
$this->getTable()->insert($this->getPayload($email, $token));我們很容易可以知道是用來向password_reset表中寫入數(shù)據(jù)的。但是我們沒有看到它指定任何模型或者表名,那么它是在哪里找到這個(gè)表的呢。我們先看getTable()方法,這個(gè)方法就定義在下方,

/**
     * 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()方法一起粘過來。我們發(fā)現(xiàn)這兩個(gè)方法都返回了該類的屬性,那么我們就去構(gòu)造方法中找傳入的依賴。

  /**
     * 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;
    }

構(gòu)造方法中果然看到傳入了一個(gè)table和一個(gè)connection,connection我們不管,是數(shù)據(jù)庫連接實(shí)例,我們找table,可是我們發(fā)現(xiàn)這里table就是一個(gè)簡單的string類型,那么我們必須找到這個(gè)DatabaseTokenRepository類實(shí)例化的地方,繼續(xù)搜索。我們找到了一個(gè)PasswordBrokerManager類里面createTokenRepository方法返回了DatabaseTokenRepository的實(shí)例對(duì)象。

/**
     * 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']
        );
    }

我們發(fā)現(xiàn)這里傳入了config數(shù)組里的table,
我們?cè)赾onfig\auth.php中找到了配置項(xiàng)

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

為什么實(shí)例對(duì)象要在這里返回呢,到這里我推測(cè)再搜索PasswordBrokerManager被引用的地方一定是一個(gè)服務(wù)提供者了。果然我們找到了Illuminate\Auth\Passwords\PasswordResetServiceProvider.php
服務(wù)容器通過這個(gè)服務(wù)提供者調(diào)用PasswordBrokerManager從而解析出DatabaseTokenRepository實(shí)例,提供依賴注入。

好現(xiàn)在我們?cè)倩氐?/p>

 $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實(shí)現(xiàn)了CanResetPassword接口,app\User.php中的User其實(shí)就是繼承了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實(shí)例,所以能夠調(diào)用notify方法,這里繼承實(shí)現(xiàn)關(guān)系比較復(fù)雜,需要自己多鉆研。剩下就不再挖掘了,這里是通過laravel框架的通知來發(fā)送重置密碼的郵件。

篇幅原因只能寫到這里,繼續(xù)深挖下去是無窮無盡的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 先說幾句廢話,調(diào)和氣氛。事情的起由來自客戶需求頻繁變更,偉大的師傅決定橫刀立馬的改革使用新的框架(created ...
    wsdadan閱讀 3,194評(píng)論 0 12
  • 簡介 laravel 使實(shí)施認(rèn)證的變得非常簡單,事實(shí)上,它提供了非常全面的配置項(xiàng)以適應(yīng)應(yīng)用的業(yè)務(wù)。認(rèn)證的配置文件存...
    Dearmadman閱讀 6,335評(píng)論 2 13
  • 過去做事情急,什么東西拿起來就用,不喜歡進(jìn)行系統(tǒng)性的學(xué)習(xí),造成在使用過程中的錯(cuò)誤和低效,現(xiàn)在感覺自己耐心多了,用之...
    馬文Marvin閱讀 2,079評(píng)論 0 10
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,564評(píng)論 19 139
  • 2016-08-17 補(bǔ)充 Exception 部分改造方案的內(nèi)容2016-08-13 補(bǔ)充 View 部分改造方...
    haolisand閱讀 5,370評(píng)論 0 16

友情鏈接更多精彩內(nèi)容