CMS 教程 - 身份验证

现在我们的 CMS 有了用户,我们可以使用 cakephp/authentication 插件来启用他们登录。我们首先要确保密码安全地存储在我们的数据库中。然后我们将提供一个可用的登录和注销功能,并允许新用户注册。

安装身份验证插件

使用 composer 安装身份验证插件

composer require "cakephp/authentication:^3.0"

添加密码哈希

您需要在数据库中创建 ControllerTableEntity 和模板,用于 users 表。您可以像以前为 ArticlesController 手动执行此操作,也可以使用 bake shell 为您生成类,方法是

bin/cake bake all users

如果您使用此设置创建或更新用户,您可能会注意到密码以纯文本形式存储。从安全角度来看,这非常不好,所以让我们修复它。

这也是谈论 CakePHP 中模型层的好时机。在 CakePHP 中,我们使用不同的类来操作记录集合和单个记录。操作实体集合的方法放在 Table 类中,而属于单个记录的功能放在 Entity 类中。

例如,密码哈希是在单个记录上完成的,因此我们将在实体对象上实现此行为。因为我们希望在每次设置密码时对其进行哈希,所以我们将使用一个变异器/设置器方法。CakePHP 会在您实体中的任何属性被设置时调用一个基于约定的设置器方法。让我们为密码添加一个设置器。在 src/Model/Entity/User.php 中添加以下内容

<?php
namespace App\Model\Entity;

use Authentication\PasswordHasher\DefaultPasswordHasher; // Add this line
use Cake\ORM\Entity;

class User extends Entity
{
    // Code from bake.

    // Add this method
    protected function _setPassword(string $password) : ?string
    {
        if (strlen($password) > 0) {
            return (new DefaultPasswordHasher())->hash($password);
        }
        return null;
    }
}

现在,将您的浏览器指向 https://127.0.0.1:8765/users 以查看用户列表。请记住,您需要运行本地服务器。使用 bin/cake server 启动独立 PHP 服务器。

您可以编辑在 安装 期间创建的默认用户。如果您更改该用户的密码,您应该在列表或查看页面上看到哈希后的密码而不是原始值。CakePHP 默认使用 bcrypt 对密码进行哈希。我们建议所有新应用程序使用 bcrypt 以保持较高的安全标准。这是 PHP 推荐的密码哈希算法

注意

现在为至少一个用户帐户创建一个哈希后的密码!它将在接下来的步骤中需要。更新密码后,您将看到一个长字符串存储在密码列中。请注意,即使对同一个密码保存两次,bcrypt 也会生成不同的哈希值。

添加登录

现在是配置身份验证插件的时候了。该插件将使用 3 个不同的类来处理身份验证过程

  • Application 将使用身份验证中间件并提供一个身份验证服务,其中包含我们要定义如何检查凭据以及在何处查找凭据的所有配置。

  • AuthenticationService 将是一个实用程序类,允许您配置身份验证过程。

  • AuthenticationMiddleware 将作为中间件队列的一部分执行,这发生在框架处理您的控制器之前,并将提取凭据并处理它们以检查用户是否已通过身份验证。

如果您还记得,我们之前使用 AuthComponent 来处理所有这些步骤。现在逻辑被分成特定的类,并且身份验证过程发生在您的控制器层之前。首先,它会检查用户是否已通过身份验证(根据您提供的配置),并将用户和身份验证结果注入请求中以供进一步引用。

src/Application.php 中,添加以下导入

// In src/Application.php add the following imports
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;

然后在您的 Application 类上实现身份验证接口

// in src/Application.php
class Application extends BaseApplication
    implements AuthenticationServiceProviderInterface
{

然后添加以下内容

// src/Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $middlewareQueue
        // ... other middleware added before
        ->add(new RoutingMiddleware($this))
        ->add(new BodyParserMiddleware())
        // Add the AuthenticationMiddleware. It should be after routing and body parser.
        ->add(new AuthenticationMiddleware($this));

    return $middlewareQueue;
}

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $authenticationService = new AuthenticationService([
        'unauthenticatedRedirect' => Router::url('/users/login'),
        'queryParam' => 'redirect',
    ]);

    // Load identifiers, ensure we check email and password fields
    $authenticationService->loadIdentifier('Authentication.Password', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ],
    ]);

    // Load the authenticators, you want session first
    $authenticationService->loadAuthenticator('Authentication.Session');
    // Configure form data check to pick email and password
    $authenticationService->loadAuthenticator('Authentication.Form', [
        'fields' => [
            'username' => 'email',
            'password' => 'password',
        ],
        'loginUrl' => Router::url('/users/login'),
    ]);

    return $authenticationService;
}

在您的 AppController 类中,添加以下代码

// src/Controller/AppController.php
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Flash');

    // Add this line to check authentication result and lock your site
    $this->loadComponent('Authentication.Authentication');

现在,在每个请求中,AuthenticationMiddleware 将检查请求会话以查找已通过身份验证的用户。如果我们正在加载 /users/login 页面,它还会检查已发布的表单数据(如果有)以提取凭据。默认情况下,凭据将从请求数据中的 usernamepassword 字段中提取。身份验证结果将被注入名为 authentication 的请求属性中。您可以使用 $this->request->getAttribute('authentication') 从您的控制器操作中随时检查结果。您所有的页面都将受到限制,因为 AuthenticationComponent 正在每个请求上检查结果。当它未能找到任何已通过身份验证的用户时,它会将用户重定向到 /users/login 页面。请注意,此时,该网站将无法正常工作,因为我们还没有登录页面。如果您访问您的网站,您将遇到“无限重定向循环”,所以让我们修复它。

注意

如果您的应用程序在 SSL 和非 SSL 协议中都提供服务,那么当您的应用程序使用非 SSL 协议时,您可能会遇到会话丢失的问题。您需要通过将 config/app.php 或 config/app_local.php 中的 session.cookie_secure 设置为 false 来启用访问。(参见 CakePHP 对 session.cookie_secure 的默认设置

在您的 UsersController 中,添加以下代码

public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);
    // Configure the login action to not require authentication, preventing
    // the infinite redirect loop issue
    $this->Authentication->addUnauthenticatedActions(['login']);
}

public function login()
{
    $this->request->allowMethod(['get', 'post']);
    $result = $this->Authentication->getResult();
    // regardless of POST or GET, redirect if user is logged in
    if ($result && $result->isValid()) {
        // redirect to /articles after login success
        $redirect = $this->request->getQuery('redirect', [
            'controller' => 'Articles',
            'action' => 'index',
        ]);

        return $this->redirect($redirect);
    }
    // display error if user submitted and authentication failed
    if ($this->request->is('post') && !$result->isValid()) {
        $this->Flash->error(__('Invalid username or password'));
    }
}

为您的登录操作添加模板逻辑

<!-- in /templates/Users/login.php -->
<div class="users form">
    <?= $this->Flash->render() ?>
    <h3>Login</h3>
    <?= $this->Form->create() ?>
    <fieldset>
        <legend><?= __('Please enter your username and password') ?></legend>
        <?= $this->Form->control('email', ['required' => true]) ?>
        <?= $this->Form->control('password', ['required' => true]) ?>
    </fieldset>
    <?= $this->Form->submit(__('Login')); ?>
    <?= $this->Form->end() ?>

    <?= $this->Html->link("Add User", ['action' => 'add']) ?>
</div>

现在登录页面将允许我们正确登录到应用程序。通过请求您网站的任何页面来测试它。在被重定向到 /users/login 页面后,输入您之前创建用户时选择的电子邮件和密码。登录成功后,您应该被成功重定向。

我们需要添加一些额外的细节来配置我们的应用程序。我们希望所有 viewindex 页面都可以在不登录的情况下访问,因此我们将此特定配置添加到 AppController 中

// in src/Controller/AppController.php
public function beforeFilter(\Cake\Event\EventInterface $event)
{
    parent::beforeFilter($event);
    // for all controllers in our application, make index and view
    // actions public, skipping the authentication check
    $this->Authentication->addUnauthenticatedActions(['index', 'view']);
}

注意

如果您还没有使用哈希后的密码的用户,请在您的 AppController 中注释掉 $this->loadComponent('Authentication.Authentication') 行,以及所有其他使用身份验证的行。然后转到 /users/add 以创建一个新用户,选择电子邮件和密码。之后,确保取消注释我们刚刚暂时注释掉的行!

在登录之前访问 /articles/add 来试一试!由于此操作不允许,您将被重定向到登录页面。成功登录后,CakePHP 将自动将您重定向回 /articles/add

注销

将注销操作添加到 UsersController 类中

// in src/Controller/UsersController.php
public function logout()
{
    $result = $this->Authentication->getResult();
    // regardless of POST or GET, redirect if user is logged in
    if ($result && $result->isValid()) {
        $this->Authentication->logout();

        return $this->redirect(['controller' => 'Users', 'action' => 'login']);
    }
}

现在您可以访问 /users/logout 以注销。然后您应该被发送到登录页面。

启用注册

如果您尝试在未登录的情况下访问 /users/add,您将被重定向到登录页面。我们应该修复这个问题,因为我们希望允许人们注册我们的应用程序。在 UsersController 中修复以下行

// Add to the beforeFilter method of UsersController
$this->Authentication->addUnauthenticatedActions(['login', 'add']);

上面的代码告诉 AuthenticationComponentUsersControlleradd() 操作不需要身份验证或授权。您可能需要花时间整理 Users/add.php 并删除误导性的链接,或者继续下一部分。我们不会在此教程中构建用户编辑、查看或列出功能,但这项练习您可以自行完成。

现在用户可以登录了,我们希望通过 应用授权策略 来限制用户只能编辑他们创建的文章。