71.邮箱认证(一)
- 本系列文章为
laracasts.com
的系列视频教程——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版 ;- 视频源码地址:github.com/laracasts/Lets-Build-a-...;
- 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 《Laravel 教程 - Web 开发实战进阶》 类似,可互相参照。
本节说明
- 对应视频教程第 71 小节:Users Must Confirm Their Email Address: #1 - Protection
本节内容
我们继续开发下一个功能:邮箱认证。按照惯例,我们新建测试:
forum\tests\Feature\CreateThreadsTest.php
.
.
/** @test */
public function guests_may_not_create_threads()
{
$this->withExceptionHandling();
$this->get('/threads/create')
->assertRedirect('/login');
$this->post('/threads')
->assertRedirect('/login');
}
/** @test */
public function authenticated_users_must_first_confirm_their_email_address_before_creating_threads()
{
$this->publishThread()
->assertRedirect('/threads')
->assertSessionHas('flash','You must first confirm your email address.');
}
.
.
接下来我们修改迁移文件,为users
表增加一个confirmed
字段:
forum\database\migrations\2014_10_12_000000_create_users_table.php
.
.
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->string('avatar_path')->nullable();
$table->boolean('confirmed')->default(false);
$table->rememberToken();
$table->timestamps();
});
}
.
.
运行迁移:
$ php artisan migrate:refresh
进入Tinker:
$ php artisan tinker
填充数据:
>>> factory('App\Thread',30)->create();
为了方便测试,我们直接在模型工厂文件中给confirmed
字段返回false
:
forum\database\factories\ModelFactory.php
.
.
$factory->define(App\User::class, function (Faker\Generator $faker) {
static $password;
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => $password ?: $password = bcrypt('123456'),
'remember_token' => str_random(10),
'confirmed' => false,
];
});
.
.
我们接下来修改控制器使测试通过:
forum\app\Http\Controllers\ThreadsController.php
.
.
public function store(Request $request)
{
if (! auth()->user()->confirmed) {
return redirect('/threads')->with('flash','You must first confirm your email address.');
}
$this->validate($request,[
'title' => 'required|spamfree',
'body' => 'required|spamfree',
'channel_id' => 'required|exists:channels,id'
]);
$thread = Thread::create([
'user_id' => auth()->id(),
'channel_id' => request('channel_id'),
'title' => request('title'),
'body' => request('body'),
]);
return redirect($thread->path())
->with('flash','Your thread has been published!');
}
.
.
运行测试:
接下来我们将利用 Laravel 中间件 功能来过滤掉未认证用户发表话题的请求。
本站教程内容引用
我们引用本站第二本教程 L02 Laravel 教程 - Web 开发实战进阶 ( Lara... 对中间件的说明:
Laravel 中间件提供了一种方便的机制来过滤进入应用的 HTTP 请求。例如,Laravel 内置了一个中间件来验证用户的身份认证。如果用户没有通过身份认证,中间件会将用户重定向到登录界面。但是,如果用户被认证,中间件将允许该请求进一步进入该应用。
当然,除了身份认证以外,中间件还可以用来执行各种任务。例如:CORS 中间件可以负责为所有离开应用的响应添加合适的头部信息;日志中间件可以记录所有传入应用的请求。Laravel 自带了一些中间件,包括身份验证、CSRF 保护等。所有这些中间件都位于 app/Http/Middleware
目录中。
Laravel 的中间件从执行时机上分『前置中间件』和『后置中间件』,前置中间件是应用初始化完成以后立刻执行,此时控制器路由还未分配、控制器还未执行、视图还未渲染。后置中间件是即将离开应用的响应,此时控制器已将渲染好的视图返回,我们可以在后置中间件里修改响应。两者的区别在于书写方式的不同:
前置中间件:
<?php
namespace App\Http\Middleware;
use Closure;
class BeforeMiddleware
{
public function handle($request, Closure $next)
{
// 这是前置中间件,在还未进入 $next 之前调用
return $next($request);
}
}
后置中间件:
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
// 这是后置中间件,$next 已经执行完毕并返回响应 $response,
// 我们可以在此处对响应进行修改。
return $response;
}
}
注意他们的区别在于
$next($request)
的执行位置,而非类的命名或者其他。
创建中间件
运行以下命令,生成中间件类文件:
$ php artisan make:middleware RedirectIfEmailNotConfirmed
注册中间件
想让中间件在应用的每个 HTTP 请求期间运行,我们还需要在 app/Http/Kernel.php
类中对中间件进行注册。
forum\app\Http\Kernel.php
<?php
namespace App\Http;
use App\Http\Middleware\RedirectIfEmailNotConfirmed;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
.
.
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'must-be-confirmed' => \App\Http\Middleware\RedirectIfEmailNotConfirmed::class
];
}
书写中间件类
forum\app\Http\Middleware\RedirectIfEmailNotConfirmed.php
<?php
namespace App\Http\Middleware;
use Closure;
class RedirectIfEmailNotConfirmed
{
public function handle($request, Closure $next)
{
if (! $request->user()->confirmed) { // 如果用户未认证,则重定向
return redirect('/threads')->with('flash','You must first confirm your email address.');
}
return $next($request);
}
}
应用中间件
我们删除控制器的代码片段:
forum\app\Http\Controllers\ThreadsController.php
.
.
public function store(Request $request)
{
$this->validate($request,[
'title' => 'required|spamfree',
'body' => 'required|spamfree',
'channel_id' => 'required|exists:channels,id'
]);
$thread = Thread::create([
'user_id' => auth()->id(),
'channel_id' => request('channel_id'),
'title' => request('title'),
'body' => request('body'),
]);
return redirect($thread->path())
->with('flash','Your thread has been published!');
}
.
.
应用中间件:
forum\routes\web.php
.
.
Route::post('threads','ThreadsController@store')->middleware('must-be-confirmed');
.
.
再次运行测试:
测试通过,但是我们还有一个小问题需要注意下:如果我们现在用一个未认证用户尝试发布话题,的确我们会被重定向至话题列表页面,但是我们存入session
的消息没有显示出来。我们期望显示消息,所以我们需要修改Flash
组件:
forum\resources\assets\js\components\Flash.vue
<template>
<div class="alert alert-flash"
:class="'alert-'+level"
role="alert"
v-show="show"
v-text="body">
</div>
</template>
<script>
export default {
props:['message'],
data(){
return {
body : this.message, // 此处赋予初始值
level : 'success',
show:false
}
},
created(){
if(this.message){
this.flash(); // 更改逻辑为:如果有消息,则展示
}
window.events.$on(
'flash',data => this.flash(data)
);
},
methods:{
flash(data){
// 修改处理方式:如果传入了对象参数,则重写 body,level
if(data) {
this.body = data.message;
this.level = data.level;
}
this.show = true;
this.hide();
},
hide(){
setTimeout( () => {
this.show = false;
},3000);
}
}
};
</script>
<style>
.alert-flash{
position: fixed;
right: 25px;
bottom: 25px;
}
</style>
现在我们再来尝试: