ホームページ >バックエンド開発 >PHPチュートリアル >Laravelには、携帯電話番号のパスワードの取得を実現するための認証パスワードリセットのソースコード分析と拡張機能が付属しています
Larval には、携帯電話番号パスワードの取得を実現するための認証パスワード リセットのソース コード分析と拡張機能が付属しています
Larval 技術グループの友人が、いつ $broker を PasswordController に設定する必要があるかを尋ねました。パスワードをリセットする理由は何ですか? Laravel の Auth の ResetsPasswords について書きたいだけなので、このブログは一般的に書きます:
まず、PasswordController の ResetsPasswords トレイトを見てみましょう。
trait ResetsPasswords { use RedirectsUsers; public function getEmail() { return $this->showLinkRequestForm(); } /** * 这里就是设置密码重置邮件内容的 * * @return \Illuminate\Http\Response */ 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 \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function postEmail(Request $request) { return $this->sendResetLinkEmail($request); } /** * 给重置密码的用户发送邮件 * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ 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 \Symfony\Component\HttpFoundation\Response */ protected function getSendResetLinkEmailSuccessResponse($response) { return redirect()->back()->with('status', trans($response)); } /** * 邮件发送时候返回 * * @param string $response * @return \Symfony\Component\HttpFoundation\Response */ protected function getSendResetLinkEmailFailureResponse($response) { return redirect()->back()->withErrors(['email' => trans($response)]); } /** * 用户点击邮箱里面重置连接后跳转的页面,就是重置密码页面 * @param \Illuminate\Http\Request $request * @param string|null $token * @return \Illuminate\Http\Response */ public function getReset(Request $request, $token = null) { return $this->showResetForm($request, $token); } /** * 用户点击邮箱里面重置连接后跳转的页面,就是重置密码页面 * * @param \Illuminate\Http\Request $request * @param string|null $token * @return \Illuminate\Http\Response */ 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 \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function postReset(Request $request) { return $this->reset($request); } /** * 重置密码实现 * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ 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 \Illuminate\Contracts\Auth\CanResetPassword $user * @param string $password * @return void */ protected function resetPassword($user, $password) { $user->password = bcrypt($password); $user->save(); Auth::guard($this->getGuard())->login($user); } //下面的代码略}
上記は実際のルーティングの実装方法です。主なルートは次のとおりです。
メソッド | URI | アクション | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| パスワード/email$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); } | AppHttpControllersAuth[email protected] | ||||||||||||
POST | <?phpnamespace Illuminate\Support\Facades;/** * @see \Illuminate\Auth\Passwords\PasswordBroker */class Password extends Facade { /** * Constant representing a successfully sent reminder. * * @var string */ const RESET_LINK_SENT = 'passwords.sent'; /** * Constant representing a successfully reset password. * * @var string */ const PASSWORD_RESET = 'passwords.reset'; /** * Constant representing the user not found response. * * @var string */ const INVALID_USER = 'passwords.user'; /** * Constant representing an invalid password. * * @var string */ const INVALID_PASSWORD = 'passwords.password'; /** * Constant representing an invalid token. * * @var string */ const INVALID_TOKEN = 'passwords.token'; protected static function getFacadeAccessor() { return 'auth.password'; }}パスワード/リセット
| AppHttpControllersAuth[email protected] | ||||||||||||
GET<?phpnamespace Illuminate\Auth\Passwords;use Illuminate\Support\ServiceProvider;class PasswordResetServiceProvider extends ServiceProvider { protected $defer = true; public function register() { $this->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']; }} |
HEAD |
<?phpnamespace Illuminate\Auth\Passwords;use InvalidArgumentException; use Illuminate\Contracts\Auth\PasswordBrokerFactory as FactoryContract;class PasswordBrokerManager implements FactoryContract { // 略 /** * 获取broker * * @param string $name * @return \Illuminate\Contracts\Auth\PasswordBroker */ 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 \Illuminate\Contracts\Auth\PasswordBroker * * @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 \Illuminate\Auth\Passwords\TokenRepositoryInterface */ 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); }}最初では、sendResetLinkEmail メソッドを見てみましょう。このメソッドは主にユーザーが入力したメール アドレスを実装します。
<?phpnamespace Illuminate\Auth\Passwords;use Closure; use Illuminate\Support\Arr; use UnexpectedValueException; use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Mail\Mailer as MailerContract; use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;class PasswordBroker implements PasswordBrokerContract { //上面的略 public function __construct(TokenRepositoryInterface $tokens, UserProvider $users, MailerContract $mailer, $emailView) { $this->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 \Illuminate\Contracts\Auth\CanResetPassword $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; } //下面的是一些验证的方法,略}See PasswordBrokerManager($app); を確認すると、上記の Passwrod::broker の実装がわかります。は PasswordBrokerManager にあるため、最初にこのパスワード リセット電子メールの送信方法を見てみましょう
Illuminate/Auth/Passwords/PasswordBrokerManager.php
電子メール検証トークンを作成し、パスワード リセット電子メールを送信します
この時点で、Laravel 独自の Auth パスワード リセットのソース コード解釈部分が完了しました。次に、これを拡張して、携帯電話番号のパスワードの取得を実現し、パスワードを取得するためのメール送信方法をカスタマイズします。上記のコードを分析すると、PasswordBroker.php と PasswordBrokerManager.php を拡張するだけで済みます。
Route::post('password/email', 'Auth\[email protected]
メール送信と携帯電話番号送信認証コードのカスタムロジッククラスは、ご自身で実装してください。 以下のコードの EmailService と SmsService は、それぞれメール送信とテキストメッセージ送信のクラスを表します。 SendCloud によるメール送信、クラウド通信による SMS 認証コード送信の具体的な実装
カスタムメールパスワードリセットのロジックは、携帯電話によるパスワードリセットのプロセスと基本的に同じで、変更されていません。番号は次のようになります:
Foundation/ ├── Auth ├── Passwords ├── RyanPasswordBroker.php ├── RyanPasswordBrokerManager.php └── Facade └── RyanPassword.php
ユーザーは携帯電話番号を入力し、「認証コードを送信」ボタンをクリックして認証コードを受け取ります
<?phpnamespace App\Providers;use App\Foundation\Auth\Passwords\RyanPasswordBrokerManager; use Illuminate\Support\ServiceProvider;class RyanPasswordResetServiceProvider extends ServiceProvider { protected $defer = true; public function register() { $this->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']; }}バックグラウンドで認証コードの認証が行われますので、新しいパスワード設定ページに飛んでも問題ありません
'providers' => [ ...... App\Providers\RyanPasswordResetServiceProvider::class, ], 'aliases' => [ ...... 'RyanPassword' => App\Foundation\Auth\Passwords\Facade\RyanPassword::class, ],
<?phpnamespace App\Foundation\Auth\Passwords\Facade;use Illuminate\Support\Facades\Password;/** * @see \Illuminate\Auth\Passwords\PasswordBroker */class RyanPassword extends Password {}
<?phpnamespace App\Foundation\Auth\Passwords;use App\Services\SmsService; use Closure; use Illuminate\Auth\Passwords\PasswordBroker; use Illuminate\Support\Arr; use UnexpectedValueException; use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Mail\Mailer as MailerContract; use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Auth\Passwords\TokenRepositoryInterface; use App\Services\EmailService; use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Support\Facades\Redis;class RyanPasswordBroker extends PasswordBroker { //这里注意下,EmailService是自定义发送邮件的方式,自己实现 public function __construct(TokenRepositoryInterface $tokens, UserProvider $users, EmailService $mailer, $emailView) { $this->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 \Illuminate\Contracts\Auth\CanResetPassword * * @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; }}
<?phpnamespace App\Foundation\Auth\Passwords;use App\Services\EmailService; use InvalidArgumentException; use Illuminate\Contracts\Auth\PasswordBrokerFactory as FactoryContract; use Illuminate\Auth\Passwords\PasswordBrokerManager as PasswordBrokerManager; use Illuminate\Auth\Passwords\DatabaseTokenRepository;class RyanPasswordBrokerManager extends PasswordBrokerManager { /** * The application instance. * * @var \Illuminate\Foundation\Application */ protected $app; /** * The array of created "drivers". * * @var array */ protected $brokers = []; protected $mailer; /** * Create a new PasswordBroker manager instance. * * @param \Illuminate\Foundation\Application $app * @return void */ public function __construct($app) { parent::__construct($app); $this->mailer = new EmailService(); } /** * Attempt to get the broker from the local cache. * * @param string $name * @return \Illuminate\Contracts\Auth\PasswordBroker */ 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 \Illuminate\Contracts\Auth\PasswordBroker * * @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']); }}
<?phpnamespace App\Controllers\Auth;use App\Controllers\Controller; use App\Requests\Auth\EmailResetPasswordRequest; use App\Requests\Auth\EmailResetPasswordSendRequest; use App\Requests\Auth\PhoneResetPasswordRequest; use App\Requests\Auth\PhoneResetPasswordSendRequest; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Illuminate\Foundation\Auth\ResetsPasswords; use RyanPassword, Password, Auth;class PasswordController extends Controller { use ResetsPasswords; protected $broker = 'users'; public function __construct() { $this->middleware('guest'); } /** * 发送重置密码邮件 * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function sendResetLinkEmail(EmailResetPasswordSendRequest $request) { $broker = $this->getBroker(); $response = RyanPassword::broker($broker)->sendResetLink($request->only('email')); switch ($response) { case Password::RESET_LINK_SENT: return ['status_code' => '200', 'message' => '密码重置邮件已发送']; case Password::INVALID_USER: default: throw new \UnauthorizedHttpException(401, '该邮箱未注册'); } } /** * 通过邮件重置密码 * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function resetBymail(EmailResetPasswordRequest $request) { $credentials = $request->only('email', 'password', 'password_confirmation', 'token'); $broker = $this->getBroker(); $response = Password::broker($broker)->reset($credentials, function ($user, $password) { $this->resetPassword($user, $password); }); switch ($response) { case Password::PASSWORD_RESET: unset($credentials['token']); unset($credentials['password_confirmation']); return [ 'status_code' => '200', 'message' => '密码重置成功' ]; case Password::INVALID_TOKEN: //返回'Token 已经失效' default: //返回'密码重置失败' } } /** * 发送重置密码短信验证码 * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function sendResetCodePhone(PhoneResetPasswordSendRequest $request) { $broker = $this->getBroker(); $response = RyanPassword::broker($broker)->sendResetCode($request->only('telephone')); switch ($response) { case Password::RESET_LINK_SENT: return ['status_code' => '200', 'message' => '密码重置验证码已发送']; case Password::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 = Password::broker($broker)->resetByPhone($credentials, function ($user, $password) { $this->resetPassword($user, $password); }); switch ($response) { case Password::PASSWORD_RESET: unset($credentials['verify_code']); unset($credentials['password_confirmation']); return [ 'status_code' => '200', 'message' => '密码重置成功', ]; case Password::INVALID_TOKEN: //返回'手机验证码已失效' default: //返回'密码重置失败' } }}app/Providers/RyanPasswordResetServiceProvider.php
config/app.php