sinbadxiii/phalcon-auth

Phalcon Auth - 基于守卫的认证

v2.1.1 2023-03-17 07:40 UTC

README

Banner

您可以在以下位置查看带有认证的应用程序示例 sinbadxiii/phalcon-auth-example

Software License Packagist Downloads Latest Version

扩展守卫

Phalcon 版本

遗憾的是,库的版本 2 已经不再支持 Phalcon 4。

支持的 PHP 版本

^7.4-8.1.

安装

使用 composer 需要的项目

composer require "sinbadxiii/phalcon-auth:^v2.0.0"

简介

Banner

Phalcon Auth 允许您在您的 Web 应用程序中创建认证系统。

认证系统包含“守卫”(Guard)和“提供者”(Provider)等概念,守卫定义了用户如何进行认证,例如使用标准的会话存储和文件cookie。

提供者定义了将作为用户使用的数据,以及从何处提取这些用户。用户数据的提取由适配器(Adapter)决定。通常这是 Phalcon\Mvc\Model 和数据库查询构建器。

此外,还有其他适配器选项:文件或包含数据的数组。您可以通过实现适配器接口创建自己的适配器。我们稍后再讨论这个问题。

守卫和提供者不应与“角色”(roles)和“权限”(permissions)混淆 ACL。如果需要更精确的应用程序节点访问封装,则应同时使用 Auth 和 ACL。例如,使用具有特定权利的 manager 角色。

快速入门

一个带有认证的完整示例应用程序可以在 sinbadxiii/phalcon-auth-example 找到。这是一个典型的 Phalcon 项目,可以用作新应用程序的起点,或者只是通过该应用程序了解认证功能。

工作原理

认证的一般原理是用户通过登录表单输入用户名和密码。如果这些凭据正确,应用程序将在用户会话中保存有关已认证用户的详细信息,并将用户视为“已认证”。如果使用“记住我”功能,可以创建一个包含会话标识符的cookie,以便后续对应用程序的请求可以关联到相应的用户。应用程序从cookie中获取会话标识符后,将提取用户数据。

考虑另一种情况,远程服务需要通过 API 进行认证以访问,通常不使用cookie进行认证,因为没有浏览器。相反,远程服务在每次请求时发送 API 令牌。应用程序可以检查输入的令牌是否在有效的 API 令牌表中,并将请求“认证”为与该 API 令牌关联的用户。

数据库准备

要使用数据库数据,需要创建一个 users 表。

如果需要使用“记住我”(RememberMe)功能,该功能允许长时间存储用户认证会话,则需要创建一个 users_remember_tokens 表,以及相应的模型 App\Models\RememberToken

要快速创建表,可以导入 db/users.sqldb/users_remember_tokens.sql 以及 db/create_auth_token_users.sql 文件,如果将 Token 用作 Guard,则需要 auth_token 字段才能正确工作。

管理员

在创建认证时,可以使用两种管理员之一:Sinbadxiii\PhalconAuth\ManagerSinbadxiii\PhalconAuth\ManagerFactory

管理员

如果您严格遵循Phalcon框架的哲学,并希望手动配置所有认证组件,则需要使用Sinbadxiii\PhalconAuth\Manager类 - 通过此管理器可以配置守卫、适配器供应商并分配用户权限。

use Sinbadxiii\PhalconAuth\Manager;
use App\Models\User;
use Sinbadxiii\PhalconAuth\Adapter\Model;
use Sinbadxiii\PhalconAuth\Guard\Session;

$auth = new Manager();

$configAdapter = [
    'model' => User::class,
];

$adapter = new Model($this->getSecurity(), $configAdapter);
$guard   = new Session(
    $adapter,
    $this->getSession(),
    $this->getCookies(),
    $this->getRequest(),
    $this->getEventsManager()
);

$auth->addGuard("web", $guard, true);

return $auth;

结果是一个通过数据库中的users表中的User模型查找用户的经理。如果选择“记住我”,认证结果将存储在会话和cookie中。需要传递以下服务提供商作为其他参数:$this->security$this->session$this->cookies$this->request$this->eventsManager,它们在使用守卫和适配器供应商时是必需的。

  • public addGuard(string $nameGuard, GuardInterface $guard, bool $isDefault = false) - 添加守卫
  • public guard(?string $name = null) - 获取特定守卫或默认守卫
  • public setDefaultGuard(GuardInterface $guard) - 设置默认守卫
  • public getDefaultGuard() - 获取默认守卫
  • public access(string $accessName) - 为控制器指定特定权限
  • public getAccess(string $accessName) - 获取指定权限
  • public setAccess(AccessInterface $access) - 获取指定权限
  • public setAccessList(array $accessList) - 注册权限列表
  • public addAccessList(array $accessList) - 添加权限列表
  • public except(...$actions) - 从权限检查中排除特定操作
  • public only(...$actions) - 必须检查权限的特定操作
  • public __call() - 魔术__call

守卫

目前有两种类型的守卫可以覆盖90%的典型Web应用程序认证创建任务。这是Sinbadxiii\PhalconAuth\Guard\SessionSinbadxiii\PhalconAuth\Guard\Token,指定其中一个守卫意味着您选择在应用程序中使用基于会话或令牌的认证。

假设您将在登录个人账户后使用会话,而令牌,例如,在作为API服务的微服务中。但是,您完全可以在非标准应用程序中使用或组合守卫。

会话守卫

use Sinbadxiii\PhalconAuth\Manager;
use App\Models\User;
use Sinbadxiii\PhalconAuth\Adapter\Model;
use Sinbadxiii\PhalconAuth\Guard\Session;

$auth = new Manager();

$configAdapter = [
    'model' => User::class,
];

$adapter = new Model($this->getSecurity(), $configAdapter);
$guard   = new Session(
    $adapter,
    $this->getSession(),
    $this->getCookies(),
    $this->getRequest(),
    $this->getEventsManager()
);

$auth->addGuard("web", $guard, true);

return $auth;
  • public function __construct(AdapterInterface $adapter, SessionManagerInterface $session, Cookies $cookies, Request $request, EventsManagerInterface $eventsManager)
  • public function attempt(array $credentials = [], $remember = false) - 尝试认证
  • public function user() - 获取已认证用户
  • public function validate(array $credentials = []) - 验证输入数据
  • public function getName() - 获取会话名称
  • public function getRememberName() - “记住我”时使用的cookie名称
  • public function login(AuthenticatableInterface $user, bool $remember = false) - 登录用户实例
  • public function loginById($id, bool $remember = false) - 通过用户ID登录
  • public function once(array $credentials = []) - 登录但不在会话中保存用户
  • public function logout() - 注销
  • public function getLastUserAttempted() - 获取最后尝试登录的用户
  • public function viaRemember() - 检查用户是否是从“记住我”中获取的
  • public function getUser() - 获取用户
  • public function setRequest(Request $request)
  • public function setSession(SessionManagerInterface $session)
  • public function setCookies(Cookies $cookies)
  • public function getAdapter() - 获取供应商适配器
  • public function setAdapter(AdapterInterface $adapter) - 指定供应商适配器

基本认证

  • public function basic(string $field = 'email', array $extraConditions = []) - 通过基本认证进行认证
  • public function onceBasic(string $field = 'email', array $extraConditions = []) - 通过基本认证进行认证但不保存到会话中

令牌守卫

要使用 Sinbadxiii\PhalconAuth\Guard\Token,需要将配置(包含请求参数名称和用户数据存储字段名称)作为第二个参数传递,例如,对于数据库中的 users 表中的字段。

[
    ... 
    'inputKey'   => 'auth_token', //имя параметра с токеном
    'storageKey' => 'auth_token', //имя поля в хранилище пользователей
    ...
]
use Sinbadxiii\PhalconAuth\Manager;
use App\Models\User;
use Sinbadxiii\PhalconAuth\Adapter\Model;
use Sinbadxiii\PhalconAuth\Guard\Token;

$auth = new Manager();

$configAdapter = [
    'model' => User::class,
];

$configGuard = [
    'inputKey'   => 'auth_token',
    'storageKey' => 'auth_token',
];

$adapter = new Model($this->getSecurity(), $configAdapter);
$guard   = new Token(
    $adapter,
    $configGuard
    $this->getRequest()
);

$auth->addGuard("api", $guard, true);

return $auth;

相应的 GET 请求应具有以下形式

//GET
https://yourapidomain/api/v2/users?auth_token=fGaYgdGPSfEgT41r3F4fg33

POST 请求

//POST
//params POST request
[
  "auth_token": "fGaYgdGPSfEgT41r3F4fg33"
]

https://yourapidomain/api/v2/users

Authorization 标题

Authorization: Bearer fGaYgdGPSfEgT41r3F4fg33

https://yourapidomain/api/v2/users

请记住,每个对应用程序的请求都必须带有 auth_token 访问令牌。

  • public function __construct(AdapterInterface $adapter, array $config, Request $request)
  • public function user() - 验证用户
  • public function validate(array $credentials = []) - 验证
  • public function getTokenForRequest() - 从请求(GET、POST、Headers)获取令牌
  • public function setRequest(Request $request)
  • public function getRequest()
  • public function getAdapter()
  • public function setAdapter(AdapterInterface $adapter)

创建自己的守卫者

<?php

declare(strict_types=1);

namespace Sinbadxiii\PhalconAuth\Guard;

use Sinbadxiii\PhalconAuth\AuthenticatableInterface;

interface GuardInterface
{
    public function check(): bool;
    public function user();
    public function setUser(AuthenticatableInterface $user);
    public function id();
    public function guest(): bool;
    public function validate(array $credentials = []): bool;
}

通过实现 Sinbadxiii\PhalconAuth\Guard\GuardInterface 接口,您可以创建自己的 Guard。

访问

通过访问(Access),您可以设置和检查对应用程序某些区域的需求访问权限,例如,在用户配置控制器中,仅允许认证用户访问。

<?php

declare(strict_types=1);

namespace App\Controllers;

class ProfileController extends ControllerBase
{
    public function onConstruct()
    {
        $this->auth->access("auth");
    }

    public function indexAction()
    {
    }
}

而注册控制器,例如,仅需要非认证用户(访客)的访问权限

<?php

declare(strict_types=1);

namespace App\Controllers;

class RegisterController extends ControllerBase
{
    public function onConstruct()
    {
        $this->auth->access("guest");
    }

    public function indexAction()
    {
    }
}

在控制器的构造函数 onConstruct() 中设置访问权限。

默认情况下,有两种主要的访问类型 - 认证和访客

  • Sinbadxiii\PhalconAuth\Access\Auth
  • Sinbadxiii\PhalconAuth\Access\Guest

如果访问条件满足 allowIf 方法,则允许进一步使用控制器,例如,在默认的 auth 条件中是

class Auth extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->check()) {
            return true;
        }

        return false;
    }
}

$this->auth->check() - 验证用户是否已认证,即要访问 $this->auth->access('auth'),则必须是认证用户,而 $this->auth->access('guest') 的条件正好相反

<?php

namespace Sinbadxiii\PhalconAuth\Access;

/**
 * Class Guest
 * @package Sinbadxiii\PhalconAuth\Access
 */
class Guest extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->guest()) {
            return true;
        }

        return false;
    }
}

如果 allowedIf() 方法返回 true,则用户可以继续前进,如果结果为 false,则触发失败方法 redirectTo(),应用程序将重定向用户,因为每个应用程序的重定向逻辑可能不同,所以您应该创建自己的 authguest 访问类,从默认类继承并重写 redirectTo() 方法

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\Auth as AuthAccess;;

class Auth extends AuthAccess
{
    public function redirectTo()
    {
        if (isset($this->response)) {
            return $this->response->redirect("/login")->send();
        }
    }
}

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\Guest as GuestAccess;

class Guest extends GuestAccess
{
    public function redirectTo()
    {
        if (isset($this->response)) {
            return $this->response->redirect("/profile")->send();
        }
    }
}

要创建自己的 Access,可以实现 Sinbadxiii\PhalconAuth\Access\AccessInterface 接口

<?php

namespace Sinbadxiii\PhalconAuth\Access;

/**
 * Interface for Sinbadxiii\PhalconAuth\Access
 */
interface AccessInterface
{
    public function setExceptActions(...$actions): void;
    public function setOnlyActions(...$actions): void;
    public function isAllowed(): bool;
    public function redirectTo();
    public function allowedIf(): bool;
}

或者简单地继承抽象类 Sinbadxiii\PhalconAuth\Access\AbstractAccess 以实现更快速和灵活的自定义访问,例如,让我们创建一个具有管理员角色的用户访问权限

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\AbstractAccess;

class Admin extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($user = $this->auth->user() and $user->getRole() === "admin") {
            return true;
        }

        return false;
    }

    /**
     * @return void
     * @throws Exception
     */
    public function redirectTo()
    {
        if (isset($this->response)) {
            return $this->response->redirect("/admin-login")->send();
        }
    }
}

或 Http Basic Auth 访问检查示例

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\AbstractAccess;
use Sinbadxiii\PhalconAuth\Exception;

class AuthWithBasic extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->basic("email")) {
            return true;
        }

        return false;
    }

    /**
     * @return void
     * @throws Exception
     */
    public function redirectTo()
    {
        throw new Exception("Basic: Invalid credentials.");
    }
}

注册访问权限

如果不在认证系统中注册访问权限,则在请求访问 $this->auth->access("auth") 时将显示错误,例如:Access with 'auth' name is not included in the access list

为了在系统中注册访问权限,需要创建某种中间件并附加到应用程序的 dispatcher

App\Security\Authenticate 的最小视图如下

<?php

declare(strict_types=1);

namespace App\Security;

use App\Security\Access\Auth;
use App\Security\Access\Guest;
use Sinbadxiii\PhalconAuth\Access\Authenticate as AuthMiddleware;

/**
 * Class Authenticate
 * @package App\Security
 */
class Authenticate extends AuthMiddleware
{
    protected array $accessList = [
        'auth'   => Auth::class,
        'guest'  => Guest::class
    ];
}

然后将其附加到服务提供者 dispatcher

$di->setShared('dispatcher', function () use ($di) {

    $dispatcher = new Phalcon\Mvc\Dispatcher();
    $eventsManager = $di->getShared('eventsManager');
    $eventsManager->attach('dispatch', new App\Security\Authenticate());
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;
});

$accessList 属性允许快速向应用程序添加新的访问级别,例如,要添加新的访问权限 admin,只需创建一个具有条件并添加到 $accessList 列表的类

<?php

declare(strict_types=1);

namespace App\Security;

use App\Security\Access\Auth;
use App\Security\Access\Admin;
use App\Security\Access\Guest;
use Sinbadxiii\PhalconAuth\Access\Authenticate as AuthMiddleware;

/**
 * Class Authenticate
 * @package App\Security
 */
class Authenticate extends AuthMiddleware
{
    protected array $accessList = [
        'auth'   => Auth::class,
        'guest'  => Guest::class,
        'admin'  => Admin::class,
    ];
}

同样,可以在创建服务提供者时直接在 Manager 中注册访问权限,使用 setAccessList() 方法

$authManager =  new Sinbadxiii\PhalconAuth\Manager();

$authManager->setAccessList(
    [
        'auth'   => App\Security\Access\Auth::class,
        'guest'  => App\Security\Access\Guest::class,
        'admin'  => App\Security\Access\Admin::class,
    ];
);
    
return $authManager;

供应商(Providers)和适配器(Adapters)

如前所述,供应商定义了哪些实体将成为用户,例如 userscontacts,这取决于您的应用程序上下文。

目前存在三种类型的适配器

  • Sinbadxiii\PhalconAuth\Adapter\Model
  • Sinbadxiii\PhalconAuth\Adapter\Stream
  • Sinbadxiii\PhalconAuth\Adapter\Memory

模型、文件和应用程序中的数据数组。所有适配器都继承自抽象类 Sinbadxiii\PhalconAuth\Adapter\AbstractAdapter,它具有

  • public setModel(AuthenticatableInterface $model)` - 指定供应商模型
  • public getModel()` - 获取供应商模型
  • public setConfig(array $config)` - 设置配置
  • public getConfig()` - 获取适配器配置

适配器提供者 Model

要使用适配器 Model,我们需要用户模型,例如 App\Models\User::class 类型

<?php

namespace App\Models;

use Phalcon\Mvc\Model;

class User extends Model
{
    public $id;
    public $username;
    public $name;
    public $email;
    public $password;
    public $published;
    public $created_at;
    public $updated_at;

    public function initialize()
    {
        $this->setSource("users");
    }
}

为了在我们的应用程序中使用,不出现错误

PHP 致命错误:未捕获的类型错误:Sinbadxiii\PhalconAuth\Adapter\Model::validateCredentials():参数 #1 ($user) 必须是 Sinbadxiii\PhalconAuth\AuthenticatableInterface 类型.

即模型 User 需要实现 Sinbadxiii\PhalconAuth\AuthenticatableInterface,如果想要使用 RememberMe (记住我) 函数的功能,还需要继承接口 Sinbadxiii\PhalconAuth\RememberingInterface:最终,您的模型应该具有以下类:

<?php

namespace App\Models;

use Phalcon\Encryption\Security\Random;
use Phalcon\Mvc\Model;
use Sinbadxiii\PhalconAuth\RememberingInterface;
use Sinbadxiii\PhalconAuth\AuthenticatableInterface;
use Sinbadxiii\PhalconAuth\RememberTokenInterface;

class User extends Model implements AuthenticatableInterface, RememberingInterface
{
    public $id;
    public $username;
    public $name;
    public $email;
    public $password;
    public $published;
    public $created_at;
    public $updated_at;

    public function initialize()
    {
        $this->setSource("users");

        $this->hasOne(
            'id',
            RememberToken::class,
            'user_id',
            [
                'alias' => "remember_token"
            ]
        );
    }

    public function setPassword(string $password)
    {
        $this->password = $this->getDI()->get("security")->hash($password);
        return $this;
    }

    public function getAuthIdentifier()
    {
        return $this->id;
    }

    public function getAuthPassword()
    {
        return $this->password;
    }

    public function getRememberToken(string $token = null): ?RememberTokenInterface
    {
        return $this->getRelated('remember_token', [
            'token=:token:',
            'bind' => ['token' => $token]
        ]);
    }

    public function setRememberToken(RememberTokenInterface $value)
    {
        $this->remember_token = $value;
    }

    public function createRememberToken(): RememberTokenInterface
    {
        $random = new Random();

        $token = $random->base64(60);

        $rememberToken = new RememberToken();
        $rememberToken->token = $token;
        $rememberToken->user_agent = $this->getDI()->get("request")->getUserAgent();
        $rememberToken->ip =  $this->getDI()->get("request")->getClientAddress();

        $this->setRememberToken($rememberToken);
        $this->save();

        return $rememberToken;
    }
}

模型 App\Models\RememberToken 将具有以下形式

<?php

declare(strict_types=1);

namespace App\Models;

use Phalcon\Mvc\Model;
use Sinbadxiii\PhalconAuth\RememberTokenInterface;

class RememberToken extends Model implements  RememberTokenInterface
{
    /**
     * @var integer
     */
    public $id;

    /**
     * @var integer
     */
    public $user_id;

    /**
     * @var string
     */
    public $token;

    /**
     * @var string
     */
    public $ip;

    /**
     * @var string
     */
    public $user_agent;

    /**
     * @var integer
     */
    public $created_at;

    /**
     * @var integer
     */
    public $updated_at;

    /**
     * @var integer
     */
    public $expired_at;

    public function initialize()
    {
        $this->setSource("users_remember_tokens");
    }

    public function getToken(): string
    {
        return $this->token;
    }

    public function getUserAgent(): string
    {
        return $this->user_agent;
    }
    
      public function beforeValidationOnCreate()
    {
        $this->created_at = date(DATE_ATOM);
        $this->updated_at = date(DATE_ATOM);
        if (!$this->expired_at) {
            $this->expired_at = date(DATE_ATOM);
        }
    }

    public function beforeValidationOnSave()
    {
        if (!$this->created_at) {
            $this->created_at = date(DATE_ATOM);
        }
        if (!$this->expired_at) {
            $this->expired_at = date(DATE_ATOM);
        }
        $this->updated_at = date(DATE_ATOM);
    }

    public function beforeValidationOnUpdate()
    {
        $this->updated_at = date(DATE_ATOM);
    }
}

接口 Sinbadxiii\PhalconAuth\AuthenticatableInterface 具有以下形式

<?php

namespace Sinbadxiii\PhalconAuth;

interface AuthenticatableInterface
{
    public function getAuthIdentifier();
    public function getAuthPassword();
}

而 "记住我" 的实现 - Sinbadxiii\PhalconAuth\RememberingInterface

<?php

namespace Sinbadxiii\PhalconAuth;

interface RememberingInterface
{
    public function getRememberToken(): ?RememberTokenInterface;
    public function createRememberToken(): RememberTokenInterface;
}

现在可以在创建管理器时使用模型

    use Sinbadxiii\PhalconAuth\Adapter\Model;
    use Sinbadxiii\PhalconAuth\Guard\Session;
    use Sinbadxiii\PhalconAuth\Manager;

    $security = $this->getSecurity();

    $adapter = new Model($security);
    $adapter->setModel(App\Models\User::class);
    $guard   = new Session(
        $adapter,
        $this->getSession(),
        $this->getCookies(),
        $this->getRequest(),
        $this->getEventsManager()
    );


    $manager = new Manager();
    $manager->addGuard("web", $guard);
    
    $manager->setDefaultGuard($guard);

    return $manager;

适配器提供者 memory

使用 setData() 可以设置包含用户的数据数组,其形式如下

[
    ["username" =>"admin", "name" => "admin", 'password' => 'admin', "email" => "admin@admin.ru"],
    ["username" => "user", "name" => "user", 'password' => 'user', "email" => "user@user.ru"],
]
$di->setShared("auth", function () {

    $security = $this->getSecurity();

    $data = [
        ["auth_token" => '1', "name" => "admin", "username" => "admin", 'password' => 'admin', "email" => "admin@admin.ru"],
        ["auth_token" => '2',  "name" => "admin1", "username" => "admin", 'password' => 'admin1', "email" => "admin1@admin.ru"],
    ];

    $adapter     = new \Sinbadxiii\PhalconAuth\Adapter\Memory($security);
    $adapter->setModel(App\Models\UserSimple::class);
    $adapter->setData($data);
    
    $configGuard = [
        'inputKey'   => 'auth_token',
        'storageKey' => 'auth_token',
    ];

    $guard = new \Sinbadxiii\PhalconAuth\Guard\Token(
        $adapter,
        $configGuard,
        $this->getRequest()
    );
    
    $manager = new Manager();
    $manager->addGuard("api", $guard, true);

    return $manager;
});
  • public setData(array $data) - 数据数组
  • public getData() - 获取数据数组

适配器提供者 Stream

如果将提供者 users 设置为 Sinbadxiii\PhalconAuth\Adapter\Stream 而不是 Sinbadxiii\PhalconAuth\Adapter\Model,则需要指定文件源格式为 json 的文件,例如 users.json,其形式如下

[
  {"name":"admin", "username":"admin", "password": "admin","email": "admin@admin.ru"},
  {"name":"user", "username":"user", "password": "user","email": "user@user.ru"}
]

返回的用户模型 App\Models\UserSimple 必须实现接口 Sinbadxiii\PhalconAuth\AuthenticatableInterface,例如

<?php

namespace App\Models;

use Phalcon\Auth\AuthenticatableInterface;

class UserSimple implements AuthenticatableInterface
{
    /**
     *
     * @var integer
     */
    public $id;

    /**
     *
     * @var string
     */
    public string $username;

    /**
     *
     * @var string
     */
    public string $name;

    /**
     *
     * @var string
     */
    public string $email;

    /**
     *
     * @var string
     */
    public string $password;

    /**
     *
     * @var integer
     */
    public $published;

    public function __construct($data)
    {
        foreach ($data as $field => $value) {
            $this->$field = $value;
        }
    }

    /**
     * @return int
     */
    public function getAuthIdentifier(): mixed
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getAuthPassword(): string
    {
        return $this->password;
    }
}

但应注意的是,不能使用 RememberMe (记住我) 函数,因为 Stream 不实现接口 Sinbadxiii\PhalconAuth\RememberingInterface,因为无法在文件存储中保存会话令牌(但这并不妨碍您在基于文件存储的自定义守卫器中实现此功能)。

    $security = $this->getSecurity();

    $adapter  = new \Sinbadxiii\PhalconAuth\Adapter\Stream($security);
    $adapter->setModel(App\Models\UserSimple::class);
    $adapter->setFileSource(__DIR__ . "/users.json");

    $guard = new \Sinbadxiii\PhalconAuth\Guard\Session(
        $adapter,
        $this->getSession(),
        $this->getCookies(),
        $this->getRequest(),
        $this->getEventsManager()
    );

    $manager = new Manager();
    $manager->addGuard("web", $guard, true); //третий аргумент - назначить охранника по дефолту

    return $manager;
  • public setFileSource(string $pathSrcFile) - 指定文件路径
  • public getFileSource() - 获取文件路径
  • public setData(array $data) - 用户数据数组
  • public getData() - 获取用户数据数组

不建议在现实应用程序中使用适配器 streammemory,因为它们的功能有限且难以管理用户。这可能在原型应用程序和不需要在数据库中存储用户的简单应用程序中很有用。

创建自己的适配器提供者

适配器提供者接口 Sinbadxiii\PhalconAuth\Adapter\AdapterInterface 具有以下形式

<?php

namespace Sinbadxiii\PhalconAuth\Adapter;

use Sinbadxiii\PhalconAuth\AuthenticatableInterface;

interface AdapterInterface
{
    public function findFirstByCredentials(array $credentials);
    public function findFirstById($id);
    public function validateCredentials(AuthenticatableInterface $user, array $credentials): bool;
}

同时,为了创建 "记住我" 功能,还需要实现接口 Sinbadxiii\PhalconAuth\Adapter\AdapterWithRememberTokenInterface

<?php

declare(strict_types=1);

namespace Sinbadxiii\PhalconAuth\Adapter;

use Sinbadxiii\PhalconAuth\AuthenticatableInterface;
use Sinbadxiii\PhalconAuth\RememberingInterface;
use Sinbadxiii\PhalconAuth\RememberTokenInterface;

interface AdapterWithRememberTokenInterface
{
    public function findFirstByToken($identifier, $token, $user_agent): ?AuthenticatableInterface;
    public function createRememberToken(RememberingInterface $user): RememberTokenInterface;
}

管理器工厂

Sinbadxiii\PhalconAuth\MangerFactory - 这是创建最小努力的认证管理器,如果您不想手动配置认证管理器,而想快速启动认证服务提供者,您可以这样做

$di->setShared('auth', function () { 
   
    $manager = new \Sinbadxiii\PhalconAuth\ManagerFactory();
    
    return $manager;
});

一切就绪,ManagerFactory 将为您处理一切,基于您的配置文件。默认情况下,使用 $this->config->auth 的配置,如果您想使用不同的配置,可以将另一个配置作为第一个参数传递

$di->setShared("auth", function () {
    $config = $this->getConfig()->auth_config_other;

    $manager = new \Sinbadxiii\PhalconAuth\ManagerFactory($config->toArray());

    return $manager;
});

使用会话的配置文件示例

因此,基于会话的应用程序配置文件的一个典型示例。该文件可以位于配置文件夹中的 config/auth.php 或全局文件 config.php 中,通过键 auth ($this->config->auth) 访问。

<?php
[
    'auth' => [
        'defaults' => [ //дефолтные значения
            'guard' => 'web'  //дефолтный охранник
        ],
        'guards' => [   //список охранников
            'web' => [          
                'driver' => 'session',   //драйвер сессия
                'provider' => 'users',   //поставщики users
            ],
        ],
        'providers' => [
            'users' => [
                'adapter' => 'model',  //адаптер поставщика users - model
                'model'  => App\Models\User::class,   //модель
            ]
        ]
    ],
];

即应用程序将使用 guard = web。Web 守卫器基于 session 驱动程序,并使用提供者 users,这些提供者通过适配器 model - App\Models\Users 提取。此配置文件允许创建不同的守卫器和提供者组合,以在您的应用程序中分离访问权限。

使用令牌的配置文件示例

<?php
[
    'auth' => [
        'defaults' => [
            'guard' => 'api'
        ],
        'guards' => [
            'api' => [
                'driver' => 'token',
                'provider' => 'users',
                'inputKey' => 'auth_token', //опционально, по дефолту auth_token
                'storageKey' => 'auth_token', //опционально, по дефолту auth_token
            ],
        ],
        'providers' => [
            'users' => [
                'adapter' => 'model',
                'model'  => App\Models\User::class,
            ]
        ]
    ],
];

可以使用 extendGuard 扩展保安,并将配置中使用的保安名称作为参数传递给新保安类,同时传递一个带有参数的 Closure,例如

$di->setShared('auth', function () {
    $auth = new \Sinbadxiii\PhalconAuth\ManagerFactory();    
    
    $request = $this->getRequest();

    $manager->extendGuard("jwt", function ($adapter, $config) use ($request) {
        return new JwtGuard($adapter, $config, $request);
    });
    
    return $auth;
});

您可以使用 extendProviderAdapter 方法扩展适配器列表,例如

$di->setShared("auth", function () {
    $authManager =  new Phalcon\Auth\ManagerFactory();

    $authManager->extendProviderAdapter("mongo", function($security, $configProvider) {
        return new App\Security\Adapter\Mongo($security, $configProvider);
    });

    return $authManager;
});

方法

设置对控制器的访问权限

access() 方法可以设置对控制器的访问权限,默认情况下 auth 用于认证用户,guest 用于访客。

$this->auth->access("auth") 
<?php

declare(strict_types=1);

namespace App\Controllers;

class ProfileController extends ControllerBase
{
    public function onConstruct()
    {
        $this->auth->access("auth");
    }

    public function indexAction()
    {
    }
}

检查当前用户是否已认证

要确定执行传入 HTTP 请求的用户是否已认证,您可以使用 check() 方法。如果用户已认证,该方法将返回 true

$this->auth->check(); 
//check authentication

例如,您可以在登录页面检查,如果用户已登录,则不显示登录表单

public function loginFormAction()
{
    if ($this->auth->check()) { 
        //redirect to profile page 
        return $this->response->redirect(
            "/profile", true
        );
    }
}

获取已认证用户

在处理传入请求时,您可以使用 user() 方法访问已认证用户。结果将是配置中 config->auth 所指定的提供者,根据 Sinbadxiii\PhalconAuth\AuthenticatableInterface 接口。

您还可以使用 id() 方法请求用户标识符(ID)

$this->auth->user(); //get the user

$this->auth->id(); //get user id

认证尝试

attempt() 方法用于处理登录表单中的认证尝试

$username = $this->request->getPost("username");
$password = $this->request->getPost("password");

//attempt login with credentials
if ($this->auth->attempt(['username' => $username, 'password' => $password])) {

 //success attempt
 ...
}

//fail attempt

attempt() 方法将第一个参数作为键值对数组。数组中的值将用于在用户数据库表中查找用户。因此,在上面的例子中,用户将通过用户名列的值获得。如果找到用户,则将数据库中存储的哈希密码与传递给方法的密码值进行比较。您不需要对传入请求的密码值进行哈希处理,因为密码已经自动哈希以与数据库中的哈希密码进行比较。如果哈希密码匹配,将为用户启动认证会话。

请记住,您的数据库用户将基于“提供者”配置进行查询。

attempt() 方法将返回 true 如果认证成功。否则将返回 false

指定额外的认证信息

您还可以在电子邮件/用户名和密码之外添加额外的请求数据。为此,只需在传递给 attempt() 方法的数组中添加查询条件。例如,我们可以检查用户是否已发布 is_published

$username = $this->request->getPost("username");
$password = $this->request->getPost("password");

//attempt login with additional credentials
if ($this->auth->attempt(['username' => $username, 'password' => $password, 'is_published' => 1])) {

 //success attempt
 ...
}

//fail attempt

"记住我"

如果您想在应用程序中实现“记住我”功能,您可以将逻辑值作为尝试方法的第二个参数传递。

如果此值等于 true,则用户将被认证为无限期或直到用户手动通过 logout() 登出。`users_remember_tokens` 表包含用于存储“记住我”令牌的列字符串。

$username = $this->request->getPost("username");
$password = $this->request->getPost("password");
$remember = this->request->getPost('remember') ? true : false;

//attempt login with credentials and remember
if ($this->auth->attempt(['username' => $username, 'password' => $password], $remember)) {

 //success attempt
 ...
}

//fail attempt

使用 viaRemember() 方法检查用户是否通过“记住我”cookie 进行认证

//use method viaRemember to check the user was authenticated using the remember me cookie
$this->auth->viaRemember();

认证用户实例

如果您需要将现有用户实例设置为当前认证用户,可以将用户实例传递给 login() 方法。此用户实例必须是 Sinbadxiii\PhalconAuth\AuthenticatableInterface 的实现。

此认证方法在您已经有了活动用户实例时非常有用,例如在用户注册到您的应用程序后立即

$user = Users::findFirst(1);
// Login and Remember the given user
$this->auth->login($user, $remember = true);

按ID认证用户

要使用数据库中的记录主键进行用户认证,您可以使用 loginById() 方法。此方法接受您想要认证的用户的主键

//and force login user by id 
$this->auth->loginById(1, true);

单次认证用户

使用 once() 方法,您可以在应用程序中为单个请求认证用户。调用此方法时,不会使用会话或cookie

//once auth without saving session and cookies
$username = $this->request->getPost("username");
$password = $this->request->getPost("password");
$this->auth->once(['username' => $username, 'password' => $password]);

退出

为了手动从您的应用程序中注销用户,您可以使用 logout() 方法。之后,用户会话中的所有认证信息将被删除,因此后续请求将不再被认证。

$this->auth->logout();
//log out user 

HTTP基本认证

基本HTTP认证 提供了一种快速的方法来认证您的应用程序用户,无需设置特殊的“登录”页面。您只需在头部传递 Authorization,值 Basic 以及用冒号分隔的电子邮件(或其它用户字段)和密码,然后使用 base64_encode() 进行编码。

$this->auth->basic("email") 方法可以创建一个Access,用于通过Auth Basic进行访问。

email 参数表示用户将通过电子邮件和密码字段进行搜索。指定其他字段,例如 username,则搜索将基于 usernamepassword 的组合。

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\AbstractAccess;
use Sinbadxiii\PhalconAuth\Exception;

class AuthWithBasic extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->basic("email")) {
            return true;
        }

        return false;
    }

    /**
     * @return void
     * @throws Exception
     */
    public function redirectTo()
    {
        throw new Exception("Basic: Invalid credentials.");
    }
}

请求后,用户信息将被记录到会话中,并且后续请求可能不再包含 Authorization 头部中的用户数据,直到会话“过期”。

无状态的基本HTTP认证

您可以使用基本HTTP认证而不在会话中保存用户。这主要用于您决定使用HTTP认证来认证对应用程序API的请求。为此,您可以创建一个调用 onceBasic() 方法的Access,例如

<?php

namespace App\Security\Access;

use Sinbadxiii\PhalconAuth\Access\AbstractAccess;
use Sinbadxiii\PhalconAuth\Exception;

class AuthWithBasic extends AbstractAccess
{
    /**
     * @return bool
     */
    public function allowedIf(): bool
    {
        if ($this->auth->onceBasic("email")) {
            return true;
        }

        return false;
    }

    /**
     * @return void
     * @throws Exception
     */
    public function redirectTo()
    {
        throw new Exception("Basic: Invalid credentials.");
    }
}

请求后, neither the cookie nor the session will contain user data, and the next request must also contain user data in the Authorization header, otherwise an exception Sinbadxiii\PhalconAuth\Exceptions will be thrown

事件

许可证

MIT许可证(MIT)。请参阅 许可证文件 获取更多信息。