使用数据库用户提供程序时如何在 Laravel 中创建密码重置方法

How to create a password reset method in Laravel when using the Database User Provider

我一直在网上搜索,但还没有找到解决以下问题的方法...

我们目前有一个使用 Laravel 开发的网站,用户 table 是远程 Microsoft SQL 数据库。 config/auth.php 中的驱动程序已设置为 "database"。除了密码重置功能外,一切正常,我们收到以下错误:

UnexpectedValueException in PasswordBroker.php line 238: User must implement CanResetPassword interface.

根据我对Laravel的有限了解(这是我第一次使用Laravel),Eloquent驱动程序支持CanResetPassword功能,但是,这还没有实现Laravel 在数据库用户提供程序中,因此出现错误。

所以我的问题是,有没有人有这样的配置,他们有“数据库”的驱动程序并实现了重置密码功能?迄今为止我看到的所有示例都与使用 Eloquent 模型有关,根据我对 Laravel 的理解,这不是一个选项,因为在初始开发期间我们不得不将驱动程序从 Eloquent 更改到数据库以使远程 Microsoft SQL 服务器首先工作。恐怕不能将 Microsoft SQL 数据库移动到本地数据库。

或者,如果有人实施了另一种用户使用电子邮件地址重置密码的方法,我愿意接受建议。

要编写您自己的密码重置逻辑,您仍然可以使用开箱即用的默认迁移或直接创建您自己的迁移。最重要的部分是令牌。因为您要自己重置密码,所以您需要做出几个决定:

  • 令牌会过期吗?
  • 一个用户可以多次使用同一个令牌吗?

您将需要在同一个控制器中包含 2 个页面、4 个不同的路由和 4 个不同的功能。 'I forgot my password' 页面和 'Reset password' 页面。在第一页中,显示一个表单,您可以在其中获取用户电子邮件。并且 post 到以下控制器。

//to be added on top as use statements 
use DB;
use Auth;
use Hash;
use Carbon;
use App\User;

public function sendPasswordResetToken(Request $request)
{
    $user = User::where ('email', $request->email)-first();
    if ( !$user ) return redirect()->back()->withErrors(['error' => '404']);

    //create a new token to be sent to the user. 
    DB::table('password_resets')->insert([
        'email' => $request->email,
        'token' => str_random(60), //change 60 to any length you want
        'created_at' => Carbon::now()
    ]);

    $tokenData = DB::table('password_resets')
    ->where('email', $request->email)->first();

   $token = $tokenData->token;
   $email = $request->email; // or $email = $tokenData->email;

   /**
    * Send email to the email above with a link to your password reset
    * something like url('password-reset/' . $token)
    * Sending email varies according to your Laravel version. Very easy to implement
    */
}

第二部分,当用户点击link

/**
 * Assuming the URL looks like this 
 * http://localhost/password-reset/random-string-here
 * You check if the user and the token exist and display a page
 */

 public function showPasswordResetForm($token)
 {
     $tokenData = DB::table('password_resets')
     ->where('token', $token)->first();

     if ( !$tokenData ) return redirect()->to('home'); //redirect them anywhere you want if the token does not exist.
     return view('passwords.show');
 }

显示包含 2 个输入的表单的页面 - 新密码 password 或任何你想要的密码 - 新密码确认 password_confirm 或任何你想要的 表单应该 post 到相同的 URL 映射到以下控制器。为什么?因为我们仍然需要使用令牌来找到实际用户。

 public function resetPassword(Request $request, $token)
 {
     //some validation
     ...

     $password = $request->password;
     $tokenData = DB::table('password_resets')
     ->where('token', $token)->first();

     $user = User::where('email', $tokenData->email)->first();
     if ( !$user ) return redirect()->to('home'); //or wherever you want

     $user->password = Hash::make($password);
     $user->update(); //or $user->save();

     //do we log the user directly or let them login and try their password for the first time ? if yes 
     Auth::login($user);

    // If the user shouldn't reuse the token later, delete the token 
    DB::table('password_resets')->where('email', $user->email')->delete();

    //redirect where we want according to whether they are logged in or not.
 }

别忘了添加路线

Route::get('password-reset', 'PasswordController@showForm'); //I did not create this controller. it simply displays a view with a form to take the email
Route::post('password-reset', 'PasswordController@sendPasswordResetToken');
Route::get('reset-password/{token}', 'PasswordController@showPasswordResetForm');
Route::post('reset-password/{token}', 'PasswordController@resetPassword');

注意:可能有错别字或语法错误,因为我没有测试,直接从头顶写到这里。如果您看到 error/exception,请不要惊慌,阅读错误并搜索 google.

只是补充一下@eddythedove 所说的内容。
我没有使用 str_random(60),而是使用 Laravel 创建令牌的方式:

private function generateToken()
{
    // This is set in the .env file
    $key = config('app.key');

    // Illuminate\Support\Str;
    if (Str::startsWith($key, 'base64:')) {
        $key = base64_decode(substr($key, 7));
    }
    return hash_hmac('sha256', Str::random(40), $key);
}

如果您在 str_random 中发现错误,请确保先导入模块:

use Illuminate\Support\Str;

然后用Str::random (60)调用。

$key = config('app.key');

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

$token = hash_hmac('sha256', Str::random(40), $key);
$dbToken = app(Hasher::class)->make($token);

DB::insert('password_resets', [
           'email' => 'email@mail.com',
            'token' => $dbToken,
        ]);

这应该适用于 Laravel 8

处理重置密码的默认方式 Laravel 存在一些安全问题。

  • 没有轨迹记录重置密码尝试(尝试成功后删除table中的token不被接受table)
  • 无有效期
  • 无令牌使用时间

我们总是更好地跟踪这些安全功能。

我在我的数据库迁移中像这样更改了默认值 table:

public function up()
{
    Schema::table('password_resets', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->enum('is_used', ['t', 'f'])->default('f');
        $table->dateTime('updated_at')->nullable();
    });
}

我没有删除记录,而是将 table 'is_used' 更新为 't' 和 updated_at 列。

我使用以下查询来过滤 is_used = 'f' 并在同一天创建以使用令牌收集。

$data = PasswordReset::where('token', $token)->where('is_used', 'f')
        ->whereDate('created_at', '>=', Carbon::today()->toDateString())->first();