简介
哈希是一种重要的安全理念,每个网页开发者都应该了解。它允许我们安全地将密码存储在数据库中。
本文不会介绍哈希是什么,如果您不熟悉它,可以查看我的Laravel中加密和哈希指南文章。
在这篇简短的文章中,我们将探讨如何在将这些模型值存储在数据库之前,自动将这些值哈希化。
手动哈希模型值
在您的Laravel代码中,您可能已经习惯了这样做,以手动哈希一个值
use App\Models\User;
use Illuminate\Support\Facades\Hash;
$user = User::create([
'name' => 'Ash',
'email' => '[email protected]',
'password' => Hash::make('password'),
]);
运行上述代码后,新用户的password
字段将存储在数据库中,类似于以下内容
$2y$10$Ugrfp6Myf9zVOo66KEuF9uQZ3hyg3T5GhJNgjOTy7o7AXCXSpwWpy
自动哈希模型值
但是,如果我们想在Laravel的User
模型中自动哈希password
字段,而不必每次手动哈希,我们应该怎么做?
为了做到这一点,我们可以使用Laravel提供的、自Laravel v10.10.0版本开始支持的hashed
模型转换。这将在值存储到数据库之前自动对字段值进行哈希处理。
让我们想象一下,我们想要更新上面的代码示例,并删除对password
字段的手动哈希。我们首先需要在我们的App\Models\User
类中指定我们想要为password
字段使用hashed
模型转换。我们可以通过如下方式更新模型的casts
属性
declare(strict_types=1);
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
final class User extends Authenticatable
{
// ...
protected $casts = [
'password'=> 'hashed',
];
}
现在,当我们创建一个新用户时,我们可以如此移除password
字段的手动哈希
use App\Models\User;
$user = User::create([
'name' => 'Ash',
'email' => '[email protected]',
'password' => 'password',
]);
值得注意的是,如果值已经被哈希,则hashed
转换不会再次对值进行哈希。因此,如果您同时使用Hash::make
和hashed
转换来手动哈希值,您不必担心值会被重复哈希。
我应该使用哪种方法?
手动哈希值的优点是它更加确切和明显,表明该值被哈希。这意味着开发者可以更容易地通过一眼理解代码在做什么。而使用hashed
模型转换时,如果没有了解模型转换,则不太明显地知道值正在被哈希。
另一方面,使用hashed
模型转换的优点是代码量更少。虽然我认为这个优点很小,因为您并没有真正地移除很多代码。
请注意,转换仅在你使用模型在数据库中创建或更新记录时应用。例如,如果您使用类似DB
门面(facade)的某物来创建用户,则他们的密码不会使用hashed
转换进行哈希。因此,在这种情况下,您需要记住手动哈希值。
在我看来,我认为两种方法都是完全可以接受的,并且各有优缺点,这完全取决于个人喜好。我只是强烈建议在您的项目中只使用一种方法,以避免任何混淆并保持一致。这将降低有人错误地认为值正在手动哈希而实际上并不是如此的可能性。
测试值是否已被哈希
无论您选择哪种方法,有一个以确保值被正确哈希的测试总是很有用的。
在您可能不小心假设值已被自动哈希但实际上并非如此的情况下,这非常有帮助。
让我们快速看一下您可以编写的基本测试,以确保值在存储在数据库中时被哈希。
我们可以设想我们有一个基本的App\Services\UserService
类,我们使用它来创建新用户。该UserService
类具有一个接受虚构的NewUserData
类的createUser
方法。该NewUserData
类有一个我们想要确保在存储到数据库之前被哈希的password
属性。
UserService
类可能看起来像这样
declare(strict_types=1);
namespace App\Services;
use App\DataTransferObjects\User;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
final readonly class UserService
{
public function createUser(NewUserData $userData): User
{
return User::create([
'name' => $userData->name,
'email' => $userData->email,
'password' => $userData->password,
]);
}
}
我们可能想要编写如下的测试
declare(strict_types=1);
namespace Tests\Feature\Services\UserService;
use App\DataObjects\NewUserData;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Support\Facades\Hash;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
final class CreateUserTest extends TestCase
{
use LazilyRefreshDatabase;
#[Test]
public function user_can_be_stored(): void
{
$newUserData = new NewUserData(
name: 'Ash',
email: '[email protected]',
password: 'password',
);
$user = (new UserService())->createUser($newUserData);
// Assert the user was stored in the database.
$this->assertDatabaseHas(User::class, [
'id' => $user->id,
'name' => 'Ash',
'email' => '[email protected]',
]);
// Assert the password has been hashed correctly.
$this->assertTrue(
Hash::check('password', $user->password),
);
}
}
在上面的测试中,我们正在调用UserService
类的createUser
方法,并传递一个包含一些虚拟数据的NewUserData
对象。我们断言用户是否已存储在数据库中。
由于我们无法直接使用assertDatabaseHas
断言比较密码的哈希值与我们传递的值,我们可以使用Hash::check
方法来检查值是否正确地被哈希。
如果值没有被哈希,则Hash::check
方法将返回false
,测试将失败。这意味着,如果在password
字段中意外地删除了哈希,我们将能够发现它。
结论
希望这篇文章能展示您如何自动哈希Laravel模型字段。
如果您喜欢阅读这篇文章,我很乐意听听您的看法。同样,如果您有任何可以改善未来文章的意见,我也很愿意听取。
您也许也对查看我的220多页的电子书感兴趣:“Battle Ready Laravel”,它更深入地涵盖了类似的主题。
或者,您也许想查看我的其他440多页的电子书:“Consuming APIs in Laravel”,它教您如何使用Laravel消费其他服务的API。
继续打造令人惊叹的产品!🚀
driesvints, ash-jc-allen, jerexbambex点了赞这篇文章