如何使用Laravel表单请求实现密码验证

来自菜鸟教程
跳转至:导航、​搜索

介绍

Laravel 表单请求 是扩展常规请求类功能的特殊类,启用 高级验证功能 。 表单请求还有助于使您的控制器操作更加简洁,因为您可以将所有验证逻辑移至表单请求类。 另一个好处是它允许您在请求到达您的控制器操作之前对其进行过滤。

在本指南中,我们将实施密码验证步骤,要求用户在访问管理区域之前确认其密码。 这种做法起到了双重检查的作用,并为您的应用程序提供了额外的安全层。

先决条件

要继续阅读本指南,您需要一个工作的 Laravel 5.6+ 应用程序,并设置了内置的 Laravel 身份验证。 有关如何设置的详细信息,请查看 官方文档

第 1 步——创建视图

我们将从设置用户的编辑个人资料页面开始。

在编写本教程时,artisan 命令实用程序不会生成视图,因此我们需要手动创建视图。

创建文件 resources/views/profile/edit.blade.php 并添加以下代码。

@extends('layouts.app')

@section('content')
<div class="container">
    @if (session('info'))
        <div class="row">
            <div class="col-md-12">
                <div class="alert alert-success alert-dismissible">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                    {{ session('info') }}
                </div>
            </div>
        </div>        
    @elseif (session('error'))
        <div class="row">
            <div class="col-md-12">
                <div class="alert alert-danger alert-dismissible">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
                    {{ session('error') }}
                </div>
            </div>
        </div>
    @endif
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Update Profile</div>

                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('profile.update', ['user' => $user]) }}">
                        {{ csrf_field() }}
                        {{ method_field('PUT') }}
                        <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
                            <label for="name" class="col-md-4 control-label">Name</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control" name="name" value="{{ $user->name }}">

                                @if ($errors->has('name'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password">

                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation">
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('current_password') ? ' has-error' : '' }}">
                            <label for="current-password" class="col-md-4 control-label">Current Password</label>

                            <div class="col-md-6">
                                <input id="current-password" type="password" class="form-control" name="current_password">

                                @if ($errors->has('current_password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('current_password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Update
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

在我们的“编辑配置文件”页面中,我们检查 infoerror 闪烁消息并将其显示给用户。

它具有 namepasswordpassword_confirmationcurrent_password 字段。

我们希望它工作的方式是每当用户进行更改时,他们必须提供正确的 current_password 字段以将更新提交到数据库。

passwordpassword_confirmation 字段将允许用户更改他们的密码。 如果它们都为空,则将保留用户的当前密码,并且不会对其存储的密码进行任何更改。

我们认为主要参与者是 passwordpassword_confirmationcurrent_password 字段。

至于 name 字段,它可以作为示例来扩展并为您的案例添加更多字段。

第 2 步 - 创建表单请求

现在进入本教程最关键的部分。

执行以下命令创建表单请求。

php artisan make:request UpdateProfile

上述命令将创建一个名为 app/Http/Requests/UpdateProfile.php 的文件。

本节中的所有代码更改都将针对此文件进行。

我们需要做的第一件事是在类声明之前为 Laravel 的 Hash 外观 别名

use Illuminate\Support\Facades\Hash;

接下来,我们需要从 authorize 方法返回 true,因为我们没有在表单请求中执行授权。

/**
 * Determine if the user is authorized to make this request.
 *
 * @return bool
 */
public function authorize()
{
    return true;
}

我们的 rules 方法将返回概述此请求的验证规则的数组。

/**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return [
        'name' => 'required|string|max:255',
        'password' => 'nullable|required_with:password_confirmation|string|confirmed',
        'current_password' => 'required',
    ];
}

namecurrent_password 规则是不言自明的。

password 规则规定密码将使用 confirmed 声明来确认。

它还声明了 required_with:password_confirmation ,这意味着如果用户提供密码确认,他们也应该提供密码。

一旦我们在控制器操作中输入提示(我们稍后会做),这些验证规则将在每次请求时自动检查。

我们需要做的最后一件事是在我们的请求中声明一个 withValidator 方法,该方法在任何验证规则触发之前传递完整构造的验证器实例。

/**
 * Configure the validator instance.
 *
 * @param  \Illuminate\Validation\Validator  $validator
 * @return void
 */
public function withValidator($validator)
{
    // checks user current password
    // before making changes
    $validator->after(function ($validator) {
        if ( !Hash::check($this->current_password, $this->user()->password) ) {
            $validator->errors()->add('current_password', 'Your current password is incorrect.');
        }
    });
    return;
 }

在我们的 withValdator 方法中,我们添加了一个 after 钩子,该函数将在完成所有验证检查后执行。

在我们的 after 挂钩中,我们将用户提供的密码与他们在数据库中设置的密码进行了比较。

$this->current_password 为我们提供了 current_password 表单字段值,而 Laravel 允许我们使用 $this->user() 访问当前经过身份验证的用户,因此 $this->user()->password 为我们提供了保存的用户哈希密码在数据库中。

使用 Hash 门面的 check 方法比较两个密码。

如果哈希检查失败,则使用 $validator->errors()->add('current_password', 'Your current password is incorrect.') 将错误添加到密钥为 current_password 的验证器中。

这是我们完整的 UpdateProfile 表单请求。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

use Illuminate\Support\Facades\Hash;

class UpdateProfile extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'password' => 'nullable|required_with:password_confirmation|string|confirmed',
            'current_password' => 'required',
        ];
    }

    /**
     * Configure the validator instance.
     *
     * @param  \Illuminate\Validation\Validator  $validator
     * @return void
     */
    public function withValidator($validator)
    {
        // checks user current password
        // before making changes
        $validator->after(function ($validator) {
            if ( !Hash::check($this->current_password, $this->user()->password) ) {
                $validator->errors()->add('current_password', 'Your current password is incorrect.');
            }
        });
        return;
    }
}

第 3 步 — 设置控制器

要使用我们的表单请求,我们需要在控制器操作中输入提示。

执行以下命令生成配置文件控制器。

php artisan make:controller ProfileController

打开文件 app/Http/Controllers/ProfileController.php 并添加以下控制器动作。

public function __construct()
{
    $this->middleware('auth');
}

/**
 * Show the form for editing the specified resource.
 *
 * @param  \App\User  $user
 * @return \Illuminate\Http\Response
 */
public function edit(Request $request, User $user)
{
    // user
    $viewData = [
        'user' => $user,
    ];
    // render view with data
    return view('profile.edit', $viewData);
}

/**
 * Update the specified resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \App\User  $user
 * @return \Illuminate\Http\Response
 */
public function update(UpdateProfile $request, User $user)
{
    // form data
    $data = $request->all();
    $user->update($data);
    return redirect(route('profile.edit', ['user' => $user]))
                ->with('info', 'Your profile has been updated successfully.');
}

配置文件控制器的构造函数设置 auth 中间件以确保用户在编辑他/她的配置文件之前已登录。

edit 操作为视图提供视图数据,而 update 操作更新用户配置文件并使用相应的 Flash 消息重定向回编辑配置文件页面。

请注意 edit 操作的签名,我们在其中输入了 UpdateProfile 请求的类型提示,这是我们在 UpdateProfile 表单请求中触发验证所需要做的一切。

您还需要在控制器类声明之前为表单请求和用户模型设置别名。

use App\Http\Requests\UpdateProfile;
use App\User;

第 4 步 — 设置受保护的路由和数据修改器

打开文件 app/routes/web.php 并添加以下代码以绑定控制器动作。

Route::get('/profile/{user}/edit', 'ProfileController@edit')->name('profile.edit');
Route::put('/profile/{user}', 'ProfileController@update')->name('profile.update');

根据我们之前在表单请求中添加的验证规则,null 密码可以通过。

在任何情况下,用户(或应用程序开发人员)都不希望将他们的密码设置为 null 或空字符串。

为了确保仅在用户提供密码时设置用户密码,我们将使用 Eloquent ORM 的 mutators

打开文件 app/User.php 并添加以下代码。

// Only accept a valid password and 
// hash a password before saving
public function setPasswordAttribute($password)
{
    if ( $password !== null & $password !== "" )
    {
        $this->attributes['password'] = bcrypt($password);
    }
}

Eloquent mutators 必须遵循命名方案 set<camel-cased-attribute-name>Attribute

由于我们为 password 属性声明了一个 mutator,因此我们将 mutator 命名为 setPasswordAttribute

mutator 函数传递了正在设置的值,在我们的 mutator 中是 $password 变量。

在我们的 mutator 中,我们检查 $password 变量是否不为 null 或空字符串,并使用 $this->attributes['password'] 在我们的模型中设置它。

另请注意,密码在保存之前已经过哈希处理,因此我们不必在应用程序的其他地方执行此操作。

默认的 Laravel Auth/RegisterControllerAuth/ResetPasswordController 在将密码持久化到数据库之前也会对密码进行哈希处理,因此我们需要更新相应控制器中的 createresetPassword 方法在声明了上述突变体之后。

打开文件 app/Http/Controllers/Auth/RegisterController.php 并添加以下代码。

/**
 * Create a new user instance after a valid registration.
 *
 * @param  array  $data
 * @return \App\User
 */
protected function create(array $data)
{
    return User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => $data['password'],
    ]);
}

打开文件 app/Http/Controllers/Auth/ResetPasswordController.php 并添加以下代码。

/**
 * Reset the given user's password.
 *
 * @param  \Illuminate\Contracts\Auth\CanResetPassword  $user
 * @param  string  $password
 * @return void
 */
protected function resetPassword($user, $password)
{
    $user->password = $password;

    $user->setRememberToken(Str::random(60));

    $user->save();

    event(new PasswordReset($user));

    $this->guard()->login($user);
}

对于 ResetPasswordController,您还需要为类声明之前使用的各个类起别名。

use Illuminate\Support\Str;
use Illuminate\Auth\Events\PasswordReset;

我们都完成了,我们的密码验证按预期工作。

结论

在本指南中,我们了解了如何实施额外的密码验证步骤来断言用户有权访问管理区域。 我们已经了解了如何创建和设置表单请求以在 Laravel 应用程序中实现表单验证。

有关验证和表单请求的更多信息,您可以查看 官方 Laravel 文档