rrd108 / api-token-authenticator
为 CakePHP 5 REST API 简单的 Token 认证插件
Requires
- cakephp/authentication: ^3.0
Requires (Dev)
- cakephp/cakephp: ^5.0
- cakephp/cakephp-codesniffer: ^5.1
- phpunit/phpunit: ^10.1
Suggests
- rrd108/cakephp-cors: Activate CORS domain in your application with Middleware
- rrd108/cakephp-json-api-exception: Make your CakePHP JSON REST API response more descriptive on errors
- rrd108/vue-bake: Bake VueJs components from CakePHP models
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,您应该执行以下步骤。
-
向您的
users
表中添加一个名为token_expiration
的列,并设置其类型为datetime
。您可以使用不同的字段名,但您必须在以下步骤中更改它。 -
在您的
config/apiTokenAuthenticator.php
文件中设置'tokenExpiration' => 'token_expiration'
。 -
更新您的
src/Model/Entity/User.php
文件,将字段添加到$accessible
数组中。
protected $_accessible = [ 'email' => true, // your other fields here 'token' => true, 'token_expiration' => true, ];
- 更新您的
src/Model/Table/UsersTable.php
文件,添加以下内容。
$validator ->dateTime('token_expiration') ->allowEmptyDateTime('token_expiration');
- 在您的
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()
方法中声明。例如,login
、register
方法是允许未经身份验证访问的好候选。
// in UsersController.php public function beforeFilter(\Cake\Event\EventInterface $event) { parent::beforeFilter($event); $this->Authentication->allowUnauthenticated(['login', 'index']); }
这将允许用户未经身份验证访问 /users/login.json
和 /users.json
URL。