跟社区学laravel博客实战6(上)

本文详细介绍了在 Laravel 中实现授权系统的过程,包括创建/生成授权策略、注册授权策略以及授权方法验证。通过授权流程确保用户只能访问和修改自己的资料,防止未登录用户和已登录用户进行非法操作。同时,文章还讨论了如何处理用户更新表单的提交,以及在更新成功后给予友好的提示和重定向。最后,对注册与登录页面进行了访问限制,确保已登录用户无法再次访问这些页面。

开始之前先说明一下,

授权系统

1创建/生成 授权策略

2注册授权策略

3authorize()方法验证

 

这里说一下,授权流程 ,先授权-->然后验证数据并 $user->update($data);

 

【先占位,此处随后上图】

 

------------------------------------------

更新用户

先创建一个新分支,并在该分支上进行功能开发:
$ git checkout master
$ git checkout -b user-crud

 

app/Http/Controllers/UsersController.php



<?php

namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
    .
    .
    .
    public function edit(User $user)
    {
        return view('users.edit', compact('user'));
    }
}

在将用户数据与视图进行绑定之后,便可以在视图上通过 $user 来访问用户对象。接下来让我们接着完成用户编辑页面的构建。

resources/views/users/edit.blade.php



@extends('layouts.default')
@section('title', '更新个人资料')

@section('content')
<div class="offset-md-2 col-md-8">
  <div class="card ">
    <div class="card-header">
      <h5>更新个人资料</h5>
    </div>
      <div class="card-body">

        @include('shared._errors')

        <div class="gravatar_edit">
          <a href="http://gravatar.com/emails" target="_blank">
            <img src="{{ $user->gravatar('200') }}" alt="{{ $user->name }}" class="gravatar"/>
          </a>
        </div>

        <form method="POST" action="{{ route('users.update', $user->id )}}">
            {{ method_field('PATCH') }}
            {{ csrf_field() }}

            <div class="form-group">
              <label for="name">名称:</label>
              <input type="text" name="name" class="form-control" value="{{ $user->name }}">
            </div>

            <div class="form-group">
              <label for="email">邮箱:</label>
              <input type="text" name="email" class="form-control" value="{{ $user->email }}" disabled>
            </div>

            <div class="form-group">
              <label for="password">密码:</label>
              <input type="password" name="password" class="form-control" value="{{ old('password') }}">
            </div>

            <div class="form-group">
              <label for="password_confirmation">确认密码:</label>
              <input type="password" name="password_confirmation" class="form-control" value="{{ old('password_confirmation') }}">
            </div>

            <button type="submit" class="btn btn-primary">更新</button>
        </form>
    </div>
  </div>
</div>
@stop

在我们提交用户更新表单之后,将由用户控制器的 update 动作来做处理,因此我们需要把表单提交的请求地址指向用户更新的 URL 上。

<form method="POST" action="{{ route('users.update', $user->id )}}">

上面代码转为 HTML 后如下所示:

<form method="POST" action="http://weibo.test/users/1">

在 RESTful 架构中,我们使用 PATCH 动作来更新资源,但由于浏览器不支持发送 PATCH 动作,因此我们需要在表单中添加一个隐藏域来伪造 PATCH 请求。

{{ method_field('PATCH') }}

转换为 HTML 代码如下所示:

<input type="hidden" name="_method" value="PATCH">

在用户注册成功之后,邮箱便不允许更改,因此我们需要给邮箱输入框加上 disabled 属性来禁止用户输入:

 <input type="text" name="email" class="form-control" value="{{ $user->email }}" disabled>

接下来让我们再来加一点样式,优化用户编辑视图。

resources/sass/app.scss



/* Users edit */

.gravatar_edit {
  margin: 15px auto;
  text-align: center;
  .gravatar {
    float: none;
    max-width: 100px;
  }
}

现在的编辑页面已能正常访问,我们需要将顶部导航栏的编辑资料链接进行更改,提供给用户访问编辑资料的入口。

resources/views/layouts/_header.blade.php


<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
  <div class="container ">
    <a class="navbar-brand" href="{{ route('home') }}">Weibo App</a>
    <ul class="navbar-nav justify-content-end">
      @if (Auth::check())
        <li class="nav-item"><a class="nav-link" href="#">用户列表</a></li>
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            {{ Auth::user()->name }}
          </a>
          <div class="dropdown-menu" aria-labelledby="navbarDropdown">
            <a class="dropdown-item" href="{{ route('users.show', Auth::user()) }}">个人中心</a>
            <a class="dropdown-item" href="{{ route('users.edit', Auth::user()) }}">编辑资料</a>
            <div class="dropdown-divider"></div>
            <a class="dropdown-item" id="logout" href="#">
              <form action="{{ route('logout') }}" method="POST">
                {{ csrf_field() }}
                {{ method_field('DELETE') }}
                <button class="btn btn-block btn-danger" type="submit" name="button">退出</button>
              </form>
            </a>
          </div>
        </li>
      @else
        <li class="nav-item"><a class="nav-link" href="{{ route('help') }}">帮助</a></li>
        <li class="nav-item" ><a class="nav-link" href="{{ route('login') }}">登录</a></li>
      @endif
    </ul>
  </div>
</nav>

 

编辑失败
现在我们已完成用户更新表单的构建,接下来需要在用户控制器加上 update 动作来处理用户提交的个人信息。

app/Http/Controllers/UsersController.php



<?php

namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
    .
    .
    .
    public function update(User $user, Request $request)
    {
        $this->validate($request, [
            'name' => 'required|max:50',
            'password' => 'required|confirmed|min:6'
        ]);

        $user->update([
            'name' => $request->name,
            'password' => bcrypt($request->password),
        ]);

        return redirect()->route('users.show', $user->id);
    }
}



我们可以看到定义的 update 方法接收两个参数,

第一个为自动解析用户 id 对应的用户实例对象,
第二个则为更新用户表单的输入数据。

编辑成功
现在的用户编辑功能还有两个地方需要优化:
在每次更改个人资料的时候都输入完整的密码,才能更新其它信息,对于不想对密码进行更新的用户,这个过程会比较繁琐;
更新成功之后在页面上没有进行任何提示,而是直接跳转到用户的个人页面,用户体验非常不好;


接下来让我们针对这两个问题对 update 方法进行优化。

app/Http/Controllers/UsersController.php



<?php

namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
    .
    .
    .
    public function update(User $user, Request $request)
    {
        $this->validate($request, [
            'name' => 'required|max:50',
            'password' => 'nullable|confirmed|min:6'
        ]);

        $data = [];
        $data['name'] = $request->name;
        if ($request->password) {
            $data['password'] = bcrypt($request->password);
        }
        $user->update($data);

        session()->flash('success', '个人资料更新成功!');

        return redirect()->route('users.show', $user);
    }
}  

首先,我们将用户密码验证的 required 规则换成 nullable,这意味着当用户提供空白密码时也会通过验证,因此我们需要对传入的 password 进行判断,当其值不为空时才将其赋值给 data,避免将空白密码保存到数据库中。
我们还通过会话闪存来添加用户资料更新成功后的消息提示。
Git 代码版本控制#
接着让我们将本次更改纳入版本控制中:

$ git add -A
$ git commit -m "更改用户资料"

 

权限系统

现在的应用存在两个巨大的安全隐患:
未登录用户可以访问 edit 和 update 动作;已登录用户可以更新其它用户的个人信息;

接下来让我们针对这两个安全隐患进行修复。

所有的中间件文件都被放在项目的 app/Http/Middleware 文件夹中。
接下来让我们使用 Laravel 提供身份验证(Auth)中间件来过滤未登录用户的 edit, update 动作。

app/Http/Controllers/UsersController.php



<?php

namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth', [            
            'except' => ['show', 'create', 'store']
        ]);
    }
    .
    .
    .
}

__construct 是 PHP 的构造器方法,当一个类对象被创建之前该方法将会被调用。

我们在 __construct 方法中调用了 middleware 方法

该方法接收两个参数

第一个为中间件的名称,

第二个为要进行过滤的动作。

我们通过 except 方法来设定 指定动作 不使用 Auth 中间件进行过滤,

意为 —— 除了此处指定的动作以外,所有其他动作都必须登录用户才能访问,

类似于黑名单的过滤机制。相反的还有 only 白名单方法,将只过滤指定动作。

我们提倡在控制器 Auth 中间件使用中,首选 except 方法,

这样的话,当你新增一个控制器方法时,默认是安全的,此为最佳实践。

 

Laravel 提供的 Auth 中间件在过滤指定动作时,如该用户未通过身份验证(未登录用户),

默认将会被重定向到 /login 登录页面。
此时退出登录,再次尝试访问 weibo.test/users/1/edit 页面将会被重定向到登录页面。

 

在完成对未登录用户的限制之后,接下来我们要限制的是已登录用户的操作,

当 id 为 1 的用户去尝试更新 id 为 2 的用户信息时,

我们应该返回一个 403 禁止访问的异常。

在 Laravel 中可以使用 授权策略 (Policy) 来对用户的操作权限进行验证,

在用户未经授权进行操作时将返回 403 禁止访问的异常。

 

1. 创建授权策略

我们可以使用以下命令来生成一个名为 UserPolicy 的授权策略类文件,用于管理用户模型的授权。

php artisan make:policy UserPolicy

所有生成的授权策略文件都会被放置在 app/Policies 文件夹下。
让我们为默认生成的用户授权策略添加 update 方法,用于用户更新时的权限验证。

 

为默认生成的用户授权策略添加 update 方法,用于用户更新时的权限验证。

app/Policies/UserPolicy.php

为默认生成的用户授权策略添加 update 方法,用于用户更新时的权限验证。

app/Policies/UserPolicy.php

为默认生成的用户授权策略添加 update 方法,用于用户更新时的权限验证。

app/Policies/UserPolicy.php

 

$ vi app/Policies/UserPolicy.php



<?php

namespace App\Policies;

use Illuminate\Auth\Access\HandlesAuthorization;
use App\Models\User;

class UserPolicy
{
    use HandlesAuthorization;

    public function update(User $currentUser, User $user)
    {
        return $currentUser->id === $user->id;
    }
}

update 方法

接收两个参数,

第一个参数默认为当前登录用户实例,

第二个参数则为要进行授权的用户实例。

当两个 id 相同时,则代表两个用户是相同用户,用户通过授权,可以接着进行下一个操作。如果 id 不相同的话,将抛出 403 异常信息来拒绝访问。

2. 注册授权策略

Laravel 提供两种注册授权策略的方式,第一种是手动指定,第二种是 Laravel  5.8 新增功能 —— 自动授权注册。为了方便起见,我们会使用第二种。

 

这里是两种写法

    protected $policies = [
        // 'App\Model' => 'App\Policies\ModelPolicy',

        //我们使用手动注册,觉得手动注册看的更明白,不至于像新手懵逼

         'App\Models\User' => 'App\Policies\UserPolicy',
    //    User::class => UserPolicy::class,
    ];



//----------------------------



    public function boot()
    {
        $this->registerPolicies();
        Gate::guessPolicyNamesUsing(function ($modelClass) {
            // 动态返回模型对应的策略名称,如:// 'App\Models\User' => 'App\Policies\UserPolicy',
//          return 'App\Policies\\'.class_basename($modelClass).'Policy';
//上面代码,实际返回的是 App\Policies\UserPolicy
//          return "App\Policies\UserPolicy";
        });
        //
    }

默认的 App\Http\Controllers\Controller 类包含了 Laravel 的 AuthorizesRequests trait。

此 trait 提供了 authorize 方法,它可以被用于快速授权一个指定的行为,当无权限运行该行为时会抛出 HttpException。

authorize 方法接收两个参数,第一个为授权策略的名称第二个为进行授权验证的数据。

我们需要为 edit 和 update 方法加上这行:

$this->authorize('update', $user);

这里 update 是指授权类里的 update 授权方法,$user 对应传参 update 授权方法的第二个参数。正如上面定义 update 授权方法时候提起的,调用时,默认情况下,我们 不需要 传递第一个参数,也就是当前登录用户至该方法内,因为框架会自动加载当前登录用户。

书写的位置如下:


app/Http/Controllers/UsersController.php



<?php

namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
    .
    .
    .
    public function edit(User $user)
    {
        $this->authorize('update', $user);
        return view('users.edit', compact('user'));
    }

    public function update(User $user, Request $request)
    {
        $this->authorize('update', $user);
        $this->validate($request, [
            'name' => 'required|max:50',
            'password' => 'nullable|confirmed|min:6'
        ]);

        $data = [];
        $data['name'] = $request->name;
        if ($request->password) {
            $data['password'] = bcrypt($request->password);
        }
        $user->update($data);

        session()->flash('success', '个人资料更新成功!');

        return redirect()->route('users.show', $user->id);
    }
}

最后,我们需要创建第二个用户来测试一下授权功能,首先让我们使用此命令进入 Tinker 环境:

php artisan tinker

如果中途想要退出 Tinker,可使用 ctrl + c 快捷键。
通过下面命令创建 ID 为 2 的用户:

App\Models\User::create(['name'=> 'Monkey', 'email'=>'monkey@example.com','password'=>bcrypt('password')])

现在,使用 id 为 1 的用户登录,当访问 id 为 2 的用户编辑页面 —— weibo.test/users/2/edit ,系统将会拒绝访问。

 

友好的转向

当一个未登录的用户尝试访问自己的资料编辑页面时,将会自动跳转到登录页面,这时候如果用户再进行登录,则会重定向到其个人中心页面上,这种方式的用户体验并不好。更好的做法是,将用户重定向到他之前尝试访问的页面,即自己的个人编辑页面。redirect() 实例提供了一个 intended 方法,该方法可将页面重定向到上一次请求尝试访问的页面上,并接收一个默认跳转地址参数,当上一次请求记录为空时,跳转到默认地址上。

app/Http/Controllers/SessionsController.php




<?php

namespace App\Http\Controllers;
.
.
.
class SessionsController extends Controller
{
    .
    .
    .
    public function store(Request $request)
    {
       $credentials = $this->validate($request, [
           'email' => 'required|email|max:255',
           'password' => 'required'
       ]);

       if (Auth::attempt($credentials, $request->has('remember'))) {
           session()->flash('success', '欢迎回来!');
           $fallback = route('users.show', Auth::user());
           return redirect()->intended($fallback);
       } else {
           session()->flash('danger', '很抱歉,您的邮箱和密码不匹配');
           return redirect()->back()->withInput();
       }
    }
    .
    .
    .
}

现在尝试退出登录,并访问 weibo.test/users/1/edit 页面,页面将重定向到登录页面,这时候接着使用 id 为 1 的用户进行登录,在登录成功后页面将重定向到用户编辑页面上

 

注册与登录页面访问限制

现在我们的应用还有一个小问题,即已登录用户还能够对注册页面和登录页面进行访问

这明显不符合常规逻辑。
我们除了可通过 Auth 中间件的 auth 属性来对控制器的一些动作进行过滤,只允许已登录用户访问之外。还可以使用 Auth 中间件提供的 guest 选项,用于指定一些只允许未登录用户访问的动作,因此我们需要通过对 guest 属性进行设置,只让未登录用户访问登录页面和注册页面。
只让未登录用户访问登录页面:

app/Http/Controllers/SessionsController.php




<?php

namespace App\Http\Controllers;
.
.
.
class SessionsController extends Controller
{
    public function __construct()
    {
        $this->middleware('guest', [
            'only' => ['create']
        ]);
    }
    .
    .
    .
}

只让未登录用户访问注册页面:

app/Http/Controllers/UsersController.php




<?php

namespace App\Http\Controllers;
.
.
.
class UsersController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth', [
            'except' => ['show', 'create', 'store']
        ]);

        $this->middleware('guest', [
            'only' => ['create']
        ]);
    }
    .
    .
    .
}    

会被跳转到 Laravel 默认指定的页面 /home ,因我们并没有此页面,所以会报错 404 找不到页面。我们需要修改下中间件里的 redirect() 方法调用,并加上友好的消息提醒:

app/Http/Middleware/RedirectIfAuthenticated.php




<?php
.
.
.
class RedirectIfAuthenticated
{
    .
    .
    .
    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            session()->flash('info', '您已登录,无需再次操作。');
            return redirect('/');
        }
        .
        .
        .
    }
}

Git 代码版本控制

接着让我们将本次更改纳入版本控制中:
 

$ git add -A
$ git commit -m "访问策略"

 

课程目录: 章节1:课前准备 课时1Laravel版本的选择05:46 课时2本地开发环境的搭建07:50 课时3本地域名解析08:40 课时4git的简单介绍08:53 课时5composer的安装和使用11:12 课时6课程源码同步08:14 课时7PHPStorm插件的安装05:44 课时8项目开发流程介绍03:19 章节2aravel基础入门 课时9Laravel的安装以及安装过程中常见问题24:40 课时10Laravel入门介绍08:46 课时11Laravel基本路由27:51 课时12Laravel路由参数32:25 课时13Laravel中间件22:43 章节3:项目模块设计和模板的引入 课时14模板的分离与blade布局模板03:12 课时15功能模块设计03:59 课时16后台控制器的创建与访问20:05 课时17后台模板的引入26:27 课时18前后台模板的获取34:33 章节4:后台-系统配置模板的开发 课时19创建站点配置页面23:57 课时20数据库连接和数据表的创建24:05 课时21网站配置信息写入数据表37:24 课时22完善网站配置(新手常见问题处理)18:59 课时23数据验证与数据闪存27:58 课时24完善系统配置功能27:59 课时25使用pjax提升后用户操作体验17:25 章节5:后台-新闻模块开发 课时26数据迁移与数据填充31:27 课时27新闻列表显示与新增36:33 课时28Laravel中的文件上传23:23 课时29富文本编辑器Neditor的使用28:56 课时30pjax下编辑器的异常处理10:07 课时31Laravel表单请求验证17:35 课时32新闻的编辑与删除34:48 课时33旧图片的处理和自定义公共函数16:10 章节6:无限级分类 课时34无限级分类原理33:06 课时35无限级分类的添加33:03 课时36无限级分类的删除与编辑20:21 课时37静态方法的正确使用07:56 课时38Laravel内置验证规则和自定义验证规则28:58 课时39提示信息的本地化11:34 章节7:后台-产品管理模块开发 课时40产品管理(1)27:23 课时41产品管理(2)-自定义验证规则25:42 课时42产品添加扩展内容14:41 课时43Laravel关联模型的使用21:33 课时44产品的编辑19:39 课时45产品编辑的完善07:13 课时46产品的批量删除与单条删除35:50 课时47使用Laravel模型事件完善产品删除功能18:37 章节8:后台-案例模块的开发 课时48数据表的创建和列表显示21:00 课时49案例的添加16:16 课时50案例的编辑与删除23:50 章节9:后台-单页模块开发 课时51单页模块(1)公司简介的处理32:11 课时52单页模块(2)招贤纳士功能实现35:00 课时53单页模块(3)发展历程的实现29:57 章节10:后台-轮播图模块开发 课时54轮播图模块表的设计与数据迁移09:19 课时55轮播图管理功能(1)20:59 课时56轮播图管理功能(2)32:43 课时57轮播图管理功能(3)自定义验证规则14:06 课时58问题处理21:57 课时59ajax实现异步排序19:42 课时60Laravel中访问器的使用11:42 课时61关于删除功能的扩展27:26 章节11:后台-友情连接模块开发 课时62友情连接模块开发28:07 章节12:后台权限认证 课时63管理员表的设计与创建15:46 课时64管理员登录验证的实现(1)25:11 课时65管理员登录验证的实现(2)23:59 章节13:前台 课时66前台首页的引入与布局模板21:01 课时67前台首页的数据调用(1)29:36 课时68前台首页的数据调用(2)16:51 课时69前台产品展示(1)23:35 课时70前台产品展示(2)15:31 课时71前台新闻数据调用17:50 课时72 Laravel自定义分页样式28:44 课时73前台新闻详情页04:33 课时74 前台案例展示11:37 课时75关于我们数据展示23:45 章节14:SEO优化入门 课时76常用的搜索引擎指令16:40 课时77SEO优化-标签优化11:46 课时78SEO优化-关键词优化08:14 课时79SEO优化-URL-robots优化07:43 课时80nofollow优化和图片优化16:43 课时81网站地图09:01 课时82链接优化与内容优化09:12 课时83百度推送(1)14:26 课时84百度推送(2)自定义类库(单例模式)27:28 课时85百度推送(3)21:13 课时86Laravel中Session的用法(百度推送优化)17:01 课时87蜘蛛来访36:09 课时88内容补充(纠错)05:18 章节15:网站安全 课时89网站安全介绍08:15 课时90上传漏洞介绍12:17 课时91常见攻击类型的防范方法06:15 课时92Laravel实现登录次数的限制19:20 课时93Laravel验证码的使用
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值