使用Laravel 5.2的内置身份验证将旧的md5密码迁移到bcrypt

时间:2016-03-17 13:00:33

标签: laravel laravel-5

我正在将一个旧的PHP应用程序迁移到Laravel 5.2。该应用程序有一个庞大的用户表(约50K用户),密码都是MD5哈希。

显然这是不可接受的,但不是向所有要求他们重置密码的所有50,000名用户发送电子邮件,我想在后台将密码更改为bcrypt哈希。

为此,我想在其中创建一个带有MD5哈希的old_password列,然后每当用户登录时,我会根据MD5哈希(如果存在)检查密码,然后创建一个新的下次使用bcrypt哈希,删除MD5哈希值。

我已经看到了一些关于如何执行此操作的示例(例如thisthis),但没有专门用于Laravel 5,也没有专门用于Laravel 5.2的内置身份验证。

有没有一种干净的方法来调整内置的auth来做这件事,或者我最好在这种情况下编写我自己的手动auth系统?

4 个答案:

答案 0 :(得分:6)

从Drupal迁移时遇到了类似的问题。我没有为旧密码创建一个新列,但更新了hasher以检查密码Drupal-way然后如果失败,请使用bcrypt进行检查。这样老用户就可以以与新用户相同的方式登录。

您需要在app中的任意位置创建一个包,比如app / packages / hashing。把这两个文件放在那里。

<强> YourHashingServiceProvider.php

<?php namespace App\Packages\Hashing;

use Illuminate\Support\ServiceProvider;

class YourHashingServiceProvider extends ServiceProvider {

    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('hash', function() { return new YourHasher; });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return ['hash'];
    }

}

<强> YourHasher.php

<?php namespace App\Packages\Hashing;

use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Hashing\BcryptHasher;
use Auth;

class YourHasher implements HasherContract
{

    protected $hasher;

    /**
     * Create a new Sha512 hasher instance.
     */
    public function __construct()
    {
        $this->hasher = new BcryptHasher;
    }

    /**
     * Hash the given value.
     *
     * @param string $value
     * @param array  $options
     *
     * @return string
     */
    public function make($value, array $options = [])
    {
        return $this->hasher->make($value, $options);
    }

    /**
     * Check the given plain value against a hash.
     *
     * @param  string $value
     * @param  string $hashedValue
     * @param  array  $options
     *
     * @return bool
     */
    public function check($value, $hashedValue, array $options = [])
    {
        return md5($value) == $hashedValue || $this->hasher->check($value, $hashedValue, $options);
    }

    /**
     * Check if the given hash has been hashed using the given options.
     *
     * @param  string $hashedValue
     * @param  array  $options
     *
     * @return bool
     */
    public function needsRehash($hashedValue, array $options = [])
    {
        return substr($hashedValue, 0, 4) != '$2y$';
    }
}

然后将App\Packages\Hashing\YourHashingServiceProvider::class放在{/ 1}}的config / app.class中。此时,您的旧用户应该能够登录您的laravel应用程序。

现在,要在用户控制器(登录/注册表单)的某处更新密码,您可以使用providersHash::needsRehash($hashed)为用户生成新的密码,然后保存。< / p>

答案 1 :(得分:2)

根据我在sustainable password hashing上阅读的文章,特别是元算法的底部位,我对解决方案的处理方式与@neochief略有不同。

我分3步完成了这个步骤:

  1. 通过将md5密码包装在bcrypt中,将bcrypt应用于数据库中的所有用户密码,就像它们是纯文本一样
  2. 当用户尝试进行身份验证时,使用guard->attempt(...)单独使用bcrypt。如果身份验证失败,则使用md5对请求中发送的密码进行双重加密,然后尝试使用guard->attempt(...)重新进行身份验证,然后将md5包装在bcrypt中进行比较。
  3. 经过身份验证后,只使用bcrypt存储纯文本密码,因此双重加密不必同时应用于同一用户。
  4. 我将AuthenticatesUsers :: login引入AuthController以使用我自己的逻辑覆盖逻辑并调用包含登录尝试逻辑的受保护方法。我正在使用JWT-Auth,但如果你不是你的解决方案,那就不会有太大的不同了。

    /**
     * Handle a login request to the application.
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function login(Request $request)
    {
        $this->validateLogin($request);
    
        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        $throttles = $this->isUsingThrottlesLoginsTrait();
    
        if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);
    
            return $this->sendLockoutResponse($request);
        }
    
        $credentials = $this->getCredentials($request);
    
        if ($token = $this->authenticate($credentials)) {
            return $this->handleUserWasAuthenticated($request, $throttles, $token);
        }
    
        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        if ($throttles && !$lockedOut) {
            $this->incrementLoginAttempts($request);
        }
    
        return $this->sendFailedLoginResponse($request);
    }
    
    /**
     * Authentication using sustainable password encryption that allows for updates to the
     * applications hash strategy that employs modern security requirements.
     * ---
     * IMPORTANT: The meta-algorithm strategy assumes that all existing passwords that use
     * an obsolete security standard for encryption have been further encrypted with an
     * up-to-date modern security standard.
     * ---
     * NOTE: Mutator has been applied to User model to store any passwords
     * that are saved using a standard for modern encryption.
     *
     * @param $credentials
     * @return string|bool
     */
    protected function authenticate($credentials)
    {
        // Attempt to authenticate using modern security standards
        $token = Auth::guard($this->getGuard())->attempt($credentials);
    
        // If the authentication failed, re-attempt using obsolete password encryption
        // to wrap the plain-text password from the request
        if ($token === false) {
    
            // Make a copy of the plain-text password
            $password = $credentials['password'];
    
            // Apply obsolete password encryption to plain-text password
            $credentials['password'] = md5($password);
    
            // Re-attempt authentication
            $token = Auth::guard($this->getGuard())->attempt($credentials);
    
            if ($token) {
    
                // Store password using modern security standard
                $user = Auth::user();
                $user->password = $password;
                $user->save();
            }
        }
    
        return $token;
    }
    

    希望这对某人有用。

答案 2 :(得分:2)

在Laravel 5.2中,你的 AuthController.php 应该覆盖登录方法,只需添加以下内容。

当登录失败时,它会尝试使用md5()登录用户。

public function login(Request $request)
{

    $this->validateLogin($request);

    // If the class is using the ThrottlesLogins trait, we can automatically throttle
    // the login attempts for this application. We'll key this by the username and
    // the IP address of the client making these requests into this application.
    $throttles = $this->isUsingThrottlesLoginsTrait();

    if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
        $this->fireLockoutEvent($request);

        return $this->sendLockoutResponse($request);
    }

    $credentials = $this->getCredentials($request);


    if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
        return $this->handleUserWasAuthenticated($request, $throttles);
    }

    //If user got here it means the AUTH was unsuccessful
    //Try to log them IN using MD5
    if($user = User::whereEmail($credentials['email'])->wherePassword(md5($credentials['password']))->first()){
        //It this condition is true, the user had the right password.

        //encrypt the password using bcrypt
        $user->password     = bcrypt($credentials['password']);
        $user->save();

        if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
            return $this->handleUserWasAuthenticated($request, $throttles);
        }

        return $this->handleUserWasAuthenticated($request, $throttles);

    }



    // If the login attempt was unsuccessful we will increment the number of attempts
    // to login and redirect the user back to the login form. Of course, when this
    // user surpasses their maximum number of attempts they will get locked out.
    if ($throttles && ! $lockedOut) {
        $this->incrementLoginAttempts($request);
    }

    return $this->sendFailedLoginResponse($request);
}

答案 3 :(得分:0)

因此,在Laravel 5.8(可能更早)中,我认为最好/最安全的解决方案是使用事件侦听器。我吞噬了其他解决方案中的一些代码...

添加到您的 app \ Providers \ EventServiceProvider.php

    protected $listen = [
...
        'Illuminate\Auth\Events\Attempting' => [
            'App\Listeners\DrupalPasswordUpdate',
        ],
...
    ];

然后创建文件 app \ Listeners \ DrupalPasswordUpdate.php

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Attempting;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class DrupalPasswordUpdate
{
    public function handle(Attempting $event)
    {
        $this->check($event->credentials['password'], \App\User::where('email', $event->credentials['email'])->first()->password??'not found');
    }

    public function check($value, $hashedValue, array $options = [])
    {
        if($this->needsRehash($hashedValue))
        {
            if($this->user_check_password($value, $hashedValue))
            {
                $newHashedValue = (new \Illuminate\Hashing\BcryptHasher)->make($value, $options);
                \Illuminate\Support\Facades\DB::update('UPDATE users SET `password` = "'.$newHashedValue.'" WHERE `password` = "'.$hashedValue.'"');
                $hashedValue = $newHashedValue;
            }
        }
    }

    public function needsRehash($hashedValue, array $options = [])
    {
        return substr($hashedValue, 0, 4) != '$2y$';
    }

    // DRUPAL PASSWORD FUNCTIONS
    function user_check_password($password, $stored_hash) {
      $hash = md5($password);
      return ($hash && $stored_hash == $hash);
    }
}

这将侦听尝试登录的尝试,在数据库中检查用户,并在适当时更新其密码,然后继续正常的登录过程。

您还可以使用其他哈希方法替换md5()调用,您可能会在Drupal安装中找到include / password.inc