摘要:到這里自帶的密碼重置的源碼解讀部分就完成了,下面我們就通過擴展一下實現手機號密碼找回和自定義郵件發送方式找回密碼,根據上面的代碼解析如果你看懂的話應該了解,其實我們只要擴展和就可以了。
本文最早于發表本人博客: Laravel 自帶 Auth 密碼重置源碼解析及擴展實現手機號密碼找回
Larval 自帶 Auth 密碼重置源碼解析及擴展實現手機號密碼找回
Larval技術群小伙伴問密碼重置時PasswordController中需要設置的$broker是干嘛用的,正好來寫一下Laravel 中Auth的ResetsPasswords,以及實踐一下擴展,所以大體這篇博客寫寫:
密碼重置源碼分析
實現自定義郵件發送方式進行密碼重置,比如使用第三方或者自己發送郵件方式找回
實現手機號密碼重置
首先來看一下PasswordController 中的 ResetsPasswords trait
trait ResetsPasswords { use RedirectsUsers; public function getEmail() { return $this->showLinkRequestForm(); } /** * 這里就是設置密碼重置郵件內容的 * * @return IlluminateHttpResponse */ public function showLinkRequestForm() { //所以我們可以在PoasswrodController 中設置 protected $linkRequestView 來定義密碼重置郵件模板 if (property_exists($this, "linkRequestView")) { return view($this->linkRequestView); } if (view()->exists("auth.passwords.email")) { return view("auth.passwords.email"); } return view("auth.password"); } /** * 發送密碼重置郵件 * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function postEmail(Request $request) { return $this->sendResetLinkEmail($request); } /** * 給重置密碼的用戶發送郵件 * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function sendResetLinkEmail(Request $request) { $this->validate($request, ["email" => "required|email"]); $broker = $this->getBroker(); //獲取broker,下面會講 $response = Password::broker($broker)->sendResetLink($request->only("email"), function (Message $message) { $message->subject($this->getEmailSubject()); }); //根據 broker 來發送密碼重置郵件,下面會詳細講 switch ($response) { case Password::RESET_LINK_SENT: //狀態,下面會講 return $this->getSendResetLinkEmailSuccessResponse($response); case Password::INVALID_USER: default: return $this->getSendResetLinkEmailFailureResponse($response); } } /** * 郵件標題 * * @return string */ protected function getEmailSubject() { return property_exists($this, "subject") ? $this->subject : "Your Password Reset Link"; } /** * 郵件成功發送過以后返回 * * @param string $response * @return SymfonyComponentHttpFoundationResponse */ protected function getSendResetLinkEmailSuccessResponse($response) { return redirect()->back()->with("status", trans($response)); } /** * 郵件發送時候返回 * * @param string $response * @return SymfonyComponentHttpFoundationResponse */ protected function getSendResetLinkEmailFailureResponse($response) { return redirect()->back()->withErrors(["email" => trans($response)]); } /** * 用戶點擊郵箱里面重置連接后跳轉的頁面,就是重置密碼頁面 * @param IlluminateHttpRequest $request * @param string|null $token * @return IlluminateHttpResponse */ public function getReset(Request $request, $token = null) { return $this->showResetForm($request, $token); } /** * 用戶點擊郵箱里面重置連接后跳轉的頁面,就是重置密碼頁面 * * @param IlluminateHttpRequest $request * @param string|null $token * @return IlluminateHttpResponse */ public function showResetForm(Request $request, $token = null) { if (is_null($token)) { return $this->getEmail(); } $email = $request->input("email"); //所以我們可以在PoasswrodController 中設置 protected $resetView 來定義密碼重置的頁面 if (property_exists($this, "resetView")) { return view($this->resetView)->with(compact("token", "email")); } if (view()->exists("auth.passwords.reset")) { return view("auth.passwords.reset")->with(compact("token", "email")); } return view("auth.reset")->with(compact("token", "email")); } /** * 重置密碼 * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function postReset(Request $request) { return $this->reset($request); } /** * 重置密碼實現 * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function reset(Request $request) { $this->validate($request, [ "token" => "required", "email" => "required|email", "password" => "required|confirmed|min:6", ]); $credentials = $request->only( "email", "password", "password_confirmation", "token" ); $broker = $this->getBroker(); $response = Password::broker($broker)->reset($credentials, function ($user, $password) { //注意這個回調 $this->resetPassword($user, $password); }); //根據 broker重置密碼,下面會詳細講 switch ($response) { case Password::PASSWORD_RESET: return $this->getResetSuccessResponse($response); default: return $this->getResetFailureResponse($request, $response); } } /** * 重置密碼,并且重新登陸 * * @param IlluminateContractsAuthCanResetPassword $user * @param string $password * @return void */ protected function resetPassword($user, $password) { $user->password = bcrypt($password); $user->save(); Auth::guard($this->getGuard())->login($user); } //下面的代碼略 }
上面其實就是路由的實現方法,主要路由如下:
Method | URI | Action |
---|---|---|
POST | password/email | AppHttpControllersAuthPasswordController@sendResetLinkEmail |
POST | password/reset | AppHttpControllersAuthPasswordController@reset |
GET | HEAD | password/reset/{token?} |
首先來主要看下sendResetLinkEmail方法,這個方法主要實現根據用戶填入的郵箱地址來發送重置郵件的
$response = Password::broker($broker)->sendResetLink($request->only("email"), function (Message $message) { $message->subject($this->getEmailSubject()); }); //根據 broker 來發送密碼重置郵件,下面會詳細講 switch ($response) { case Password::RESET_LINK_SENT: //狀態,下面會講 return $this->getSendResetLinkEmailSuccessResponse($response); case Password::INVALID_USER: default: return $this->getSendResetLinkEmailFailureResponse($response); }
上面的Password 就是Facade,我們看一下這個Facade:
Illuminate/Support/Facades/Password.php
可以看到上面郵件發送后等狀態的判斷也是在這個Facade中定義的,那么auth.password 的這個是綁定到哪個類中實現的?繼續查看對應的ServiceProvider的register
Illuminate/Auth/Passwords/PasswordResetServiceProvider.php
registerPasswordBroker(); } protected function registerPasswordBroker() { $this->app->singleton("auth.password", function ($app) { return new PasswordBrokerManager($app); }); $this->app->bind("auth.password.broker", function ($app) { return $app->make("auth.password")->broker(); }); } public function provides() { return ["auth.password", "auth.password.broker"]; } }看到了PasswordBrokerManager($app);,那么我們就知道了上面Passwrod::broker 的實現在PasswordBrokerManager中,那我們先來看下是如何發送這個重置密碼郵件的
Illuminate/Auth/Passwords/PasswordBrokerManager.php
getDefaultDriver(); return isset($this->brokers[$name]) ? $this->brokers[$name] : $this->brokers[$name] = $this->resolve($name); } /** * Resolve the given broker. * * @param string $name * @return IlluminateContractsAuthPasswordBroker * * @throws InvalidArgumentException */ protected function resolve($name) { $config = $this->getConfig($name); //獲取auth.php配置中的passwords broker if (is_null($config)) { throw new InvalidArgumentException("Password resetter [{$name}] is not defined."); } //這里很重要,就是實例一個PasswordBroker return new PasswordBroker( $this->createTokenRepository($config), $this->app["auth"]->createUserProvider($config["provider"]), $this->app["mailer"], $config["email"] ); } /** * 根據配置創建一個token實例 * * @param array $config * @return IlluminateAuthPasswordsTokenRepositoryInterface */ protected function createTokenRepository(array $config) { return new DatabaseTokenRepository( $this->app["db"]->connection(), $config["table"], $this->app["config"]["app.key"], $config["expire"] ); } //下面略 public function __call($method, $parameters) { return call_user_func_array([$this->broker(), $method], $parameters); } }上面的resolve返回了new PasswordBroker,這里的PasswordBroker其實才是密碼重置的核心實現,里面主要做了實現一下幾件事情:
創建郵件驗證的token,并發送重置密碼郵件
用戶點擊重置連接以后根據token進行驗證
重置舊的密碼成用戶提交的新密碼
Illuminate/Auth/Passwords/PasswordBroker.php
users = $users; $this->mailer = $mailer; $this->tokens = $tokens; $this->emailView = $emailView; } /** * 給用戶發送包含重置鏈接的郵件 * * @param array $credentials * @param Closure|null $callback * @return string */ public function sendResetLink(array $credentials, Closure $callback = null) { // 驗證用戶 $user = $this->getUser($credentials); if (is_null($user)) { return PasswordBrokerContract::INVALID_USER; } // 生成token $token = $this->tokens->create($user); //發送郵件 $this->emailResetLink($user, $token, $callback); return PasswordBrokerContract::RESET_LINK_SENT; } /** * 發送郵件的實現 * * @param IlluminateContractsAuthCanResetPassword $user * @param string $token * @param Closure|null $callback * @return int */ public function emailResetLink(CanResetPasswordContract $user, $token, Closure $callback = null) { //把token和user變量傳遞到郵件模板中,并發送郵件 $view = $this->emailView; return $this->mailer->send($view, compact("token", "user"), function ($m) use ($user, $token, $callback) { $m->to($user->getEmailForPasswordReset()); if (! is_null($callback)) { call_user_func($callback, $m, $user, $token); } }); } /** * 根據token重置密碼 * * @param array $credentials * @param Closure $callback * @return mixed */ public function reset(array $credentials, Closure $callback) { //實現根據$credentials來驗證用戶是否可以更改更改密碼 $user = $this->validateReset($credentials); if (! $user instanceof CanResetPasswordContract) { return $user; } $pass = $credentials["password"]; // 下面這個就是產生新的密碼的實現 call_user_func($callback, $user, $pass); $this->tokens->delete($credentials["token"]); return PasswordBrokerContract::PASSWORD_RESET; } //下面的是一些驗證的方法,略 }上面的reset 中的call_user_func就是調用了重置新密碼的邏輯,$callback其實就是最上面的trait ResetsPasswords 中的resetPassword($user, $password)來保存新密碼。
到這里Laravel 自帶Auth的密碼重置的源碼解讀部分就完成了,下面我們就通過擴展一下實現手機號密碼找回和自定義郵件發送方式找回密碼,根據上面的代碼解析如果你看懂的話應該了解,其實我們只要擴展PasswordBroker.php和PasswordBrokerManager.php就可以了。
自定義郵件發送和手機號發送驗證碼邏輯類請自行實現,以下代碼的EmailService和SmsService分別表示發送郵件和發送短信的類,自己按照需求進行封裝,比如SendCloud發送郵件,云通訊發送手機短信驗證碼的具體實現
自定義郵件重置密碼的邏輯基本都一樣的,不變,手機號重置密碼的過程應該是這樣的:
用戶填入手機號,點擊“發送驗證碼”按鈕,收到驗證碼
將驗證碼填入,點擊“密碼找回”
后臺進行驗證碼校驗,沒有問題跳轉到新密碼設置頁面
新密碼設置
路由如下:
Route::post("password/email", "AuthPasswordController@sendResetLinkEmail"); //通過郵件重置密碼 Route::post("password/reset-mail", "AuthPasswordController@resetBymail"); //發送手機短信驗證碼 Route::post("password/phone", "AuthPasswordController@sendResetCodePhone"); //通過手機驗證碼找回密碼 Route::post("password/reset-phone", "AuthPasswordController@resetByPhone");在app目錄下建立入如下目錄和文件(根據個人習慣):
Foundation/ ├── Auth ?? ├── Passwords ?? ?? ├── RyanPasswordBroker.php ?? ?? ├── RyanPasswordBrokerManager.php ?? ?? └── Facade ?? ?? └── RyanPassword.php新建ServiceProvider,將auth.password綁定到我們自己的RyanPasswordBroker
app/Providers/RyanPasswordResetServiceProvider.php
registerPasswordBroker(); } protected function registerPasswordBroker() { $this->app->singleton("auth.password", function ($app) { return new RyanPasswordBrokerManager($app); }); $this->app->bind("auth.password.broker", function ($app) { return $app->make("auth.password")->broker(); }); } public function provides() { return ["auth.password", "auth.password.broker"]; } }修改config/app.php
"providers" => [ ...... AppProvidersRyanPasswordResetServiceProvider::class, ], "aliases" => [ ...... "RyanPassword" => AppFoundationAuthPasswordsFacadeRyanPassword::class, ],app/Foundation/Auth/Passwords/Facade/RyanPassword.php
app/Foundation/Auth/Passwords/RyanPasswordBroker.php
users = $users; $this->mailer = $mailer; $this->tokens = $tokens; $this->emailView = $emailView; } public function emailResetLink(CanResetPasswordContract $user, $token, Closure $callback = null) { $body = app("view")->make($this->emailView, compact("token", "user"))->render(); return $this->mailer->send($user->getEmailForPasswordReset(), "xxx賬號密碼重置", $body, $fromName = "xxxx"); } protected function validateReset(array $credentials) { if (is_null($user = $this->getUser($credentials))) { return PasswordBrokerContract::INVALID_USER; } if (!$this->validateNewPassword($credentials)) { return PasswordBrokerContract::INVALID_PASSWORD; } if (isset($credentials["verify_code"])) { //如果提交的字段含有verify_code表示是手機驗證碼方式重置密碼,需要驗證用戶提交的驗證碼是不是剛才發送給他手機號的,驗證碼發送以后可以保持在緩存中 if (Redis::get("password:telephone:" . $credentials["telephone"]) != $credentials["verify_code"]) { return PasswordBrokerContract::INVALID_TOKEN; } } elseif (!$this->tokens->exists($user, $credentials["token"])) { //郵件重置方式 return PasswordBrokerContract::INVALID_TOKEN; } return $user; } /** * Get the user for the given credentials. * * @param array $credentials * @return IlluminateContractsAuthCanResetPassword * * @throws UnexpectedValueException */ public function getUser(array $credentials) { $credentials = Arr::except($credentials, ["token", "verify_code"]);//這里注意,如果是手機驗證碼方式找回密碼需要吧verify_code字段排除,以免users表中沒有verify_code字段查不到用戶 $user = $this->users->retrieveByCredentials($credentials); if ($user && !$user instanceof CanResetPasswordContract) { throw new UnexpectedValueException("User must implement CanResetPassword interface."); } return $user; } /** * 發送重置密碼手機驗證碼 * * @param array $credentials * @param Closure|null $callback * @return string */ public function sendResetCode(array $credentials, Closure $callback = null) { $user = $this->getUser($credentials); if (is_null($user)) { return PasswordBrokerContract::INVALID_USER; } //我是將手機驗證碼發送后保持在Redis中,驗證的時候也是去redis取 $telephone = $credentials["telephone"]; $code = random_int(100000, 999999); $result = with(new SmsService())->sendTemplateSms($telephone, config("sms.template_ids.password_verify_code"), [$code]); $result = json_decode($result, true); if ($result["status"]) { Redis::setEx("password:telephone:" . $telephone, 3000, $code); return PasswordBrokerContract::RESET_LINK_SENT; } } /** * 通過手機驗證碼重置密碼 * @param array $credentials * @param Closure $callback * @return CanResetPasswordContract|string */ public function resetByPhone(array $credentials, Closure $callback) { $user = $this->validateReset($credentials); if (!$user instanceof CanResetPasswordContract) { return $user; } $pass = $credentials["password"]; call_user_func($callback, $user, $pass); //如果是手機號重置密碼的話新密碼保存后需要刪除緩存的驗證碼 Redis::del("password:telephone:" . $credentials["telephone"]); return PasswordBrokerContract::PASSWORD_RESET; } }app/Foundation/Auth/Passwords/RyanPasswordBrokerManager.php
mailer = new EmailService(); } /** * Attempt to get the broker from the local cache. * * @param string $name * @return IlluminateContractsAuthPasswordBroker */ public function broker($name = null) { $name = $name ?: $this->getDefaultDriver(); return isset($this->brokers[$name]) ? $this->brokers[$name] : $this->brokers[$name] = $this->resolve($name); } /** * Resolve the given broker. * * @param string $name * @return IlluminateContractsAuthPasswordBroker * * @throws InvalidArgumentException */ protected function resolve($name) { $config = $this->getConfig($name); if(is_null($config)) { throw new InvalidArgumentException("Password resetter [{$name}] is not defined."); } //這里實例化我們自定義的RyanPasswordBroker來完成密碼重置邏輯 return new RyanPasswordBroker($this->createTokenRepository($config), $this->app["auth"]->createUserProvider($config["provider"]), $this->mailer, $config["email"]); } }修改PasswordController.php
middleware("guest"); } /** * 發送重置密碼郵件 * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function sendResetLinkEmail(EmailResetPasswordSendRequest $request) { $broker = $this->getBroker(); $response = RyanPassword::broker($broker)->sendResetLink($request->only("email")); switch ($response) { case RyanPassword::RESET_LINK_SENT: return ["status_code" => "200", "message" => "密碼重置郵件已發送"]; case RyanPassword::INVALID_USER: default: throw new UnauthorizedHttpException(401, "該郵箱未注冊"); } } /** * 通過郵件重置密碼 * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function resetBymail(EmailResetPasswordRequest $request) { $credentials = $request->only("email", "password", "password_confirmation", "token"); $broker = $this->getBroker(); $response = RyanPassword::broker($broker)->reset($credentials, function ($user, $password) { $this->resetPassword($user, $password); }); switch ($response) { case RyanPassword::PASSWORD_RESET: unset($credentials["token"]); unset($credentials["password_confirmation"]); return [ "status_code" => "200", "message" => "密碼重置成功" ]; case RyanPassword::INVALID_TOKEN: //返回"Token 已經失效" default: //返回"密碼重置失敗" } } /** * 發送重置密碼短信驗證碼 * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ public function sendResetCodePhone(PhoneResetPasswordSendRequest $request) { $broker = $this->getBroker(); $response = RyanPassword::broker($broker)->sendResetCode($request->only("telephone")); switch ($response) { case RyanPassword::RESET_LINK_SENT: return ["status_code" => "200", "message" => "密碼重置驗證碼已發送"]; case RyanPassword::INVALID_USER: default: //返回"該手機號未注冊" } } /** * 通過短信驗證碼重置密碼 * @param PhoneResetPasswordRequest $request * @return array */ public function resetByPhone(PhoneResetPasswordRequest $request) { $credentials = $request->only("telephone", "password", "password_confirmation", "verify_code"); $broker = $this->getBroker(); $response = RyanPassword::broker($broker)->resetByPhone($credentials, function ($user, $password) { $this->resetPassword($user, $password); }); switch ($response) { case RyanPassword::PASSWORD_RESET: unset($credentials["verify_code"]); unset($credentials["password_confirmation"]); return [ "status_code" => "200", "message" => "密碼重置成功", ]; case RyanPassword::INVALID_TOKEN: //返回"手機驗證碼已失效" default: //返回"密碼重置失敗" } } }結束!!
轉載請注明:?轉載自Ryan是菜鳥 | LNMP技術棧筆記
如果覺得本篇文章對您十分有益,何不 打賞一下
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30325.html
摘要:系統的核心是由的認證組件的看守器和提供器組成。使用的認證系統,幾乎所有東西都已經為你配置好了。其配置文件位于,其中包含了用于調整認證服務行為的注釋清晰的選項配置。 用戶認證系統(基礎介紹) 使用過Laravel的開發者都知道,Laravel自帶了一個認證系統來提供基本的用戶注冊、登錄、認證、找回密碼,如果Auth系統里提供的基礎功能不滿足需求還可以很方便的在這些基礎功能上進行擴展。這篇...
摘要:微信任意用戶密碼修改漏洞漏洞描述在微信官方的首頁上發現了找回密碼功能。選擇通過手機號碼找回密碼。提交成功,輸入新密碼。網易郵箱可直接修改其他用戶密碼描述這次我們看一個郵箱的找回密碼漏洞,這個還真和上面的方式有點不一樣。 來自 GitChat 作者:湯青松更多使用技術,盡在微信公眾號:GitChat技術雜談 進入 GitChat 閱讀原文 WEB安全用戶密碼找回多案例安全攻防實戰 這次文...
摘要:擴展用戶認證系統上一節我們介紹了系統實現的一些細節知道了是如何應用看守器和用戶提供器來進行用戶認證的,但是針對我們自己開發的項目或多或少地我們都會需要在自帶的看守器和用戶提供器基礎之上做一些定制化來適應項目,本節我會列舉一個在做項目時遇到的 擴展用戶認證系統 上一節我們介紹了Laravel Auth系統實現的一些細節知道了Laravel是如何應用看守器和用戶提供器來進行用戶認證的,但是...
摘要:學習了一段時間的小結一下最近做的小任務寫下來才知道好亂糟糟,還是以記錄學習的資料為主,寫的很糟糕,還需要再揣度多屢屢思路。 學習了一段時間的laravel,小結一下最近做的laravel小任務,寫下來才知道好亂糟糟,還是以記錄學習的資料為主,寫的很糟糕,還需要再揣度多屢屢思路。20151103-16 源碼地址:https://github.com/dingyiming/xc-add...
摘要:如果兩個經哈希運算的密碼相匹配那么將會為這個用戶開啟一個認證。如果認證成功的話方法將會返回。重定向器上的方法將用戶重定向到登錄之前用戶想要訪問的,在目標無效的情況下回退將會傳遞給該方法。最后如有錯誤,歡迎指出交流群 Auth認證 路由 從路由開始,找到源碼,再進行研究找到根目錄下面的 vendor/laravel/framework/src/Illuminate/Routing/Rou...
閱讀 1618·2021-11-22 13:53
閱讀 2848·2021-11-15 18:10
閱讀 2755·2021-09-23 11:21
閱讀 2491·2019-08-30 15:55
閱讀 475·2019-08-30 13:02
閱讀 752·2019-08-29 17:22
閱讀 1659·2019-08-29 13:56
閱讀 3455·2019-08-29 11:31