在 laravel 中进行加密电子邮件验证



在 laravel 身份验证过程中,我想加密用户表中的电子邮件字段(我使用了 Crypt::encrypt())。执行此操作时,登录过程将失败。我必须在登录时验证加密的电子邮件。有人可以帮助我吗?

我不认为迭代数据库数据是可扩展的。就我而言,我还加密了来自其他实体的其他字段,因此在这方面,我的答案是基于@Eskaaa的答案。

我创建了一个打印 IV 的命令,并将该命令在名为APP_IV的属性中打印的值添加到我的.env

public function handle()
{
echo base64_encode(random_bytes(openssl_cipher_iv_length($this->laravel['config']['app.cipher'])));
return Command::SUCCESS;
}

然后,我将以下行添加到我的config/app.php

'iv' => env('APP_IV'),

现在,我已经创建了从IlluminateEncryptionEncrypter类继承的MyCustomEncrypter类。Encrypter负责实现Laravel的加密解密。我需要重写加密方法并在构造函数中加载 IV。

class MyCustomEncrypter extends Encrypter
{
private $iv;
public function __construct()
{
$this->iv = base64_decode(config('app.iv'));
$key = base64_decode(Str::after(config('app.key'), 'base64:'));
parent::__construct($key, config('app.cipher'));
}
public function encrypt($value, $serialize = true)
{
$iv = $this->iv;
$value = openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'), JSON_UNESCAPED_SLASHES);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
}

为了加密数据,我创建了一个自定义 Cast,并设置了将在模型中加密的属性:

class EncrypterCast implements CastsAttributes
{
private $myEncrypter;
public function __construct()
{
$this->myEncrypter = new MyCustomEncrypter;
}
public function get($model, string $key, $value, array $attributes)
{
return $this->myEncrypter->decryptString($value);
}
public function set($model, string $key, $value, array $attributes)
{
return $this->myEncrypter->encryptString($value);
}
}

用户型号:

class User extends Authenticatable implements MustVerifyEmail
{
...
protected $casts = [
'name' => EncrypterCast::class,
'email' => EncrypterCast::class,
'email_verified_at' => 'datetime',
];
...
}

我使用Laravel Breeze生成登录,登录验证和密码恢复屏幕。要遵循的所有步骤都基于Laravel Breeze代码。

登录

我需要修改AppHttpRequestsAuthLoginRequest类的authenticate方法。结果是:

public function authenticate()
{
$this->ensureIsNotRateLimited();

$credentials = $this->only('email', 'password');
if (array_key_exists('email', $credentials)) {
$encrypter = new MyCustomEncrypter;
$credentials['email'] = $encrypter->encryptString($credentials['email']);
}
if (! Auth::attempt($credentials, $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}

登录验证

不需要更改,因为 Laravel 使用User::email属性;强制转换自身解密数据。

密码恢复

我没有加密密码恢复表中的电子邮件;这样做需要做很多工作,每当使用令牌时,它都会清除数据库中的注册表。鉴于此,我只需要更改两种方法。AppHttpControllersAuthPasswordResetLinkController类的store方法和AppHttpControllersAuthNewPasswordController类的store方法。

PasswordResetLinkController.php

public function store(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
]);
$credentials = $request->only('email');
if (array_key_exists('email', $credentials)) {
$encrypter = new MyCustomEncrypter;
$credentials['email'] = $encrypter->encryptString($credentials['email']);
}
$status = Password::sendResetLink($credentials );
return $status == Password::RESET_LINK_SENT
? back()->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}

NewPasswordController.php

public function store(Request $request)
{
$request->validate([
'token' => ['required'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', RulesPassword::defaults()],
]);
$credentials = $request->only('email', 'password', 'password_confirmation', 'token');
if (array_key_exists('email', $credentials)) {
$encrypter = new MyCustomEncrypter;
$credentials['email'] = $encrypter->encryptString($credentials['email']);
}

$status = Password::reset(
$credentials,
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
return $status == Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}

做!我的Laravel版本是8.x

如果我确实很好地理解了您的问题,那么解决它的一个好方法是使用装饰器模式! 由于Laravel提供了一种轻松创建中间件的方法,我们可以将它们用作装饰器。

您需要做的是添加一个名为encryptEmails的中间件,正如我们所知,中间件可以访问请求对象! 我们现在需要做的是从请求中检索电子邮件值并通过加密来更改它。

当然,您必须在登录路由中包含此中间件。

使用此方法,无需更改内置身份验证类的内容。

这是中间件句函数的代码(我不确定加密电子邮件的类或方法):

public function handle($request, Closure $next)
{
$request->email = Crypt::encrypt($request->email)
return $next($request);
}

您可以查看此链接以获取有关Laravel中间件的更多信息: https://laravel.com/docs/5.7/middleware

在这种情况下,您应该允许用户使用用户名登录。覆盖LoginController中的username()方法:

public function username()
{
return 'username';
}

如果您仍然想使用电子邮件进行身份验证,则需要加载所有用户,然后检查每个用户的解密电子邮件,如果您注册了许多用户,这是一个坏主意。

$users = User::all();
foreach ($users as $user) {
if ($request->email === decrypt($user->email) && Hash::check($request->password, $user->password)) {
auth()->login($user); // Login the user if email and password are correct
break; // Exit from the foreach loop
}
}

您还可以对数据进行分块。

同样,此解决方案仅适用于少数注册用户。

我在LoginController中使用了以下代码,并在 Laravel 5.6 中为我工作,

public function attemptLogin(Request $request) {
$users = User::all();
$isUserValidated=false;
$field = $request->username;
foreach ($users as $user) {
try { // required if the field is not encrypted
// login using username or email
if (($field === Crypt::decryptString($user->email) || $field === Crypt::decryptString($user->username)) && Hash::check($request->password, $user->password)) {
$isUserValidated=true;
$this->guard()->login($user,false);
break; // Exit from the foreach loop
}
} catch (DecryptException $e) {
//
}
}
return $isUserValidated;
}

我认为能够加密电子邮件以进行登录很重要。Laravel的问题在于它总是使用不同的初始化向量。

若要变通解决此问题,您可以创建两个使用固定初始化向量的方法。这样,您就不必遍历所有用户。您可以将来自请求的电子邮件与数据库记录进行比较。

从模型读取时,必须始终解密电子邮件。保存时,必须始终对其进行加密。如果使用唯一规则和电子邮件规则进行验证,则必须分两个步骤验证用户数据。首先使用规则电子邮件来验证格式,然后加密传递的电子邮件,然后通过规则进行验证。

我希望我能帮上一点忙。

注意:这不是一个现成的解决方案,只是一个代码示例作为一个想法。如果您尝试此解决方案,则需要注意密码代理和密码重置。


/** 
* Get a new initialization vector 
* Store this initialization vector in your app-config (/config/app.php)
**/
function getNewIv() :string
{
return openssl_random_pseudo_bytes(16);
}
/** encrypt **/
function encryptEmail(string $email): string
{
$cipher = config('app.cipher', 'AES-256-CBC');
$key = config('app.key');
$iv= config('app.iv');
$encrypted = openssl_encrypt($email, $cipher, $key, 1, base64_decode($iv));
return base64_encode($encrypted);
}

/** decrypt **/
function decryptEmail(string $email): string
{
$data = base64_decode($decryptString);
$cipher = config('app.cipher', 'AES-256-CBC');
$key = config('app.key');
$iv= config('app.iv');
return openssl_decrypt($data, $cipher, $key, 1, base64_decode($iv));
}

最新更新