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

开始之前先说明一下,

授权系统

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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值