rrd108/api-token-authenticator

为 CakePHP 5 REST API 简单的 Token 认证插件

安装量: 1,595

依赖者: 0

建议者: 0

安全性: 0

星星: 3

关注者: 2

分支: 0

开放问题: 4

类型:cakephp-plugin

1.1.0 2024-03-04 16:31 UTC

This package is auto-updated.

Last update: 2024-09-04 17:31:19 UTC


README

为 CakePHP 5 REST API 提供简单的 Token 认证插件。

关于 CakePHP 4 版本请查看 rrd108/api-token-authenticator

对于 REST API,您可能希望使用像 rrd108/cakephp-cors 这样的 cors 插件和像 rrd108/cakephp-json-api-exception 这样的 json api 异常插件。

如果您使用 vuejs 作为前端,您可能希望使用 rrd108/vue-bake 来制作您的 vue 组件。

配置

Users

在您的 users 表中,您应该有一个名为 token 的字段,或您选择的任何名称。在下面的示例中,我们将使用 token。插件不会自动生成 token 的值。您可以在 UsersController.php 文件中的 login() 方法(或您想要的其他地方)中生成它。请看下面的示例。

修改默认设置

如果您对默认设置满意,您可以跳过本节。

默认设置请参见插件目录中的 config/apiTokenAuthenticator.php 文件。

如果您想更改任何值,则在您的项目 config 目录中创建自己的 config/apiTokenAuthenticator.php 文件。在您的配置文件中,您应该仅使用您想要更改的键。它将合并到默认设置中。所以,例如,如果您对所有的选项都感到满意,除了您的 token 的头名称是 Authorization 之外,那么您必须将此放入您的配置文件中。

<?php
return [
  'ApiTokenAuthenticator' => [
    'header' => 'Authorization',
  ]
];

使用 Bearer 令牌进行授权

如果您想使用带有 Bearer 令牌的 Authorization 头,请在您的 config/apiTokenAuthenticator.php 文件中将 header 键设置为 Authorization,并将 prefix 键设置为 Bearer

<?php
return [
  'ApiTokenAuthenticator' => [
    'header' => 'Authorization',
    'prefix' => 'Bearer',
  ]
];

认证

插件的认证工作流程如下。

在您的客户端应用程序中,您应该向 /users/login.json 发送 POST 请求(或您在 config/apiTokenAuthenticator.php 文件中设置的值)带有如下 JSON 对象。

{
    "email": "rrd@webmania.cc",
    "password": "rrd"
}

如果登录成功,您将得到如下响应。

{
    "user": {
        "id": 1,
        "token": "yourSecretTokenComingFromTheDatabase"
    }
}

然后您可以使用此 token 来进行身份验证,以便访问需要身份验证的 URL。该 token 应当以名为 Token 的请求头(或您在 config/apiTokenAuthenticator.php 文件中设置的值)发送。

安装

1. 安装插件

包含插件的方式与每个其他 CakePHP 插件几乎相同

composer require rrd108/api-token-authenticator

然后,要加载插件,运行以下命令之一

bin/cake plugin load ApiTokenAuthenticator

或者手动将以下行添加到您的应用的 config/plugins.php 文件中

return [
  // other plugins
  'ApiTokenAuthenticator' => [],
];

2. 禁用 CSRF 保护

您应该在 /src/Application.php 文件中的 middleware() 方法中注释掉(或删除)CsrfProtectionMiddleware

3. 加载插件的组件

在您的 AppController.php 文件的 initialize() 函数中应包含以下组件

public function initialize(): void
{
  parent::initialize();
  $this->loadComponent('Authentication.Authentication');
}

并将 JSON 视图支持添加到 AppController.php

use Cake\View\JsonView;

public function viewClasses(): array
{
  return [JsonView::class];
}

4. 设置密码哈希器

更新您的 src/Model/Entity/User.php 文件,添加以下内容。

use Authentication\PasswordHasher\DefaultPasswordHasher;
protected function _setPassword(string $password)
{
  $hasher = new DefaultPasswordHasher();
  return $hasher->hash($password);
}

别忘了从 $_hidden 数组中移除 token 字段。

5. 为 routes 设置扩展

由于您可能使用 JSON URL,别忘了将以下行添加到您的 config/routes.php 文件中。

$routes->scope('/', function (RouteBuilder $builder): void {
  // other routes
  $builder->setExtensions(['json']);
  $builder->resources('Users');

  $builder->fallbacks();
});

5. 在控制器中设置 JSON 响应

在您的控制器中,您应该设置 JSON 响应类型。

// for example in UsersController.php
public function index()
{
  $query = $this->Users->find();
  $users = $this->paginate($query);

  $this->set(compact('users'));
  $this->viewBuilder()->setOption('serialize', ['users']);
}

由于 CakePHP 响应使用内容类型协商,添加 Accept: application/json 头到您的请求中是很重要的。

这就完成了。应该可以正常运行。

login() 方法

如果您使用静态 tokens

登录方法不会自动添加,您应该实现它。以下是一个示例。

public function login()
{
  $result = $this->Authentication->getResult();
  if ($result->isValid()) {
    $user = $this->Authentication->getIdentity()->getOriginalData();
    $this->set(compact('user'));
    $this->viewBuilder()->setOption('serialize', ['user']);
  }
}

login 方法应该添加到允许未经身份验证访问的操作列表中。

public function beforeFilter(\Cake\Event\EventInterface $event)
{
  parent::beforeFilter($event);
  $this->Authentication->allowUnauthenticated(['login']);
}

如果您使用动态 tokens

public function login()
{
  $result = $this->Authentication->getResult();
  if ($result->isValid()) {
    $user = $this->Authentication->getIdentity()->getOriginalData();
    $user->token = $this->generateToken();
    $user = $this->Users->save($user);
    $user = $this->Users->get($user->id);

    $this->set(compact('user'));
    $this->viewBuilder()->setOption('serialize', ['user']);
  }
  // if login failed you can throw an exception, suggested: rrd108/cakephp-json-api-exception
}

private function generateToken(int $length = 36)
{
  $random = base64_encode(Security::randomBytes($length));
  $cleaned = preg_replace('/[^A-Za-z0-9]/', '', $random);
  return substr($cleaned, 0, $length);
}

Token 过期

默认情况下,插件不会使 token 失效,您可以使用它们永久或像上面的示例代码那样,直到没有新的登录会话。

如果您希望插件仅在特定时间段内使用 tokens,您应该执行以下步骤。

  1. 向您的 users 表中添加一个名为 token_expiration 的列,并设置其类型为 datetime。您可以使用不同的字段名,但您必须在以下步骤中更改它。

  2. 在您的 config/apiTokenAuthenticator.php 文件中设置 'tokenExpiration' => 'token_expiration'

  3. 更新您的 src/Model/Entity/User.php 文件,将字段添加到 $accessible 数组中。

protected $_accessible = [
  'email' => true,
  // your other fields here
  'token' => true,
  'token_expiration' => true,
];
  1. 更新您的 src/Model/Table/UsersTable.php 文件,添加以下内容。
$validator
  ->dateTime('token_expiration')
  ->allowEmptyDateTime('token_expiration');
  1. 在您的 src/Controller/UsersController.php 文件中,您应该修改 login() 方法。
public function login()
{
  $result = $this->Authentication->getResult();
  if ($result->isValid()) {
    $user = $this->Authentication->getIdentity()->getOriginalData();
    list($user->token, $user->token_expiration) = $this->generateToken();
    $user = $this->Users->save($user);

    $this->set(compact('user'));
    $this->viewBuilder()->setOption('serialize', ['user']);

    // delete all expired tokens
    $this->Users->updateAll(
      ['token' => null, 'token_expiration' => null],
      ['token_expiration <' => Chronos::now()]
    );
  }
}

private function generateToken(int $length = 36, string $expiration = '+6 hours')
{
  $random = base64_encode(Security::randomBytes($length));
  $cleaned = preg_replace('/[^A-Za-z0-9]/', '', $random);
  return [$cleaned, strtotime($expiration)];
}

未经身份验证访问

如果您想允许用户未经身份验证访问资源,您应该在控制器中的 beforeFilter() 方法中声明。例如,loginregister 方法是允许未经身份验证访问的好候选。

// in UsersController.php
public function beforeFilter(\Cake\Event\EventInterface $event)
{
  parent::beforeFilter($event);
  $this->Authentication->allowUnauthenticated(['login', 'index']);
}

这将允许用户未经身份验证访问 /users/login.json/users.json URL。