tobento / app-user
为具有认证和授权的应用提供用户支持。
Requires
- php: >=8.0
- tobento/app: ^1.0.7
- tobento/app-country: ^1.0
- tobento/app-database: ^1.0
- tobento/app-http: ^1.0
- tobento/app-migration: ^1.0
- tobento/app-validation: ^1.0
- tobento/service-acl: ^1.0
- tobento/service-clock: ^1.0
- tobento/service-config: ^1.0
- tobento/service-repository: ^1.0
- tobento/service-repository-storage: ^1.0
- tobento/service-user: ^1.0.1
Requires (Dev)
- mockery/mockery: ^1.6
- nyholm/psr7: ^1.4
- phpunit/phpunit: ^9.5
- tobento/app-console: ^1.0
- tobento/app-event: ^1.0.1
- tobento/service-console: ^1.0.1
- tobento/service-container: ^1.0
- vimeo/psalm: ^4.0
README
为具有认证和授权的应用提供用户支持。
目录
入门
运行此命令添加正在运行的应用用户项目最新版本。
composer require tobento/app-user
需求
- PHP 8.0或更高版本
文档
应用
如果您正在使用骨架,请查看应用骨架。
您还可以查看应用以了解更多有关该应用的信息。
用户启动
用户启动执行以下操作
- 安装和加载用户配置文件
- 根据配置实现用户和角色仓库
- 根据配置添加认证和授权中间件
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\User\Boot\User::class); // Run the app $app->run();
用户配置
用户的配置位于默认应用骨架配置位置下的app/config/user.php
文件中。
用户仓库和工厂
以下仓库和工厂根据app/config/user.php
配置文件定义可用。默认仓库实现使用在app/config/database.php
文件中定义的Tobento\Service\Storage\StorageInterface::class
作为存储,默认为文件存储。
use Tobento\App\AppFactory; use Tobento\App\User\UserRepositoryInterface; use Tobento\App\User\UserFactoryInterface; use Tobento\App\User\AddressRepositoryInterface; use Tobento\App\User\AddressFactoryInterface; use Tobento\App\User\RoleRepositoryInterface; use Tobento\App\User\RoleFactoryInterface; use Tobento\Service\Repository\RepositoryInterface; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // User: $userRepository = $app->get(UserRepositoryInterface::class); // var_dump($userRepository instanceof RepositoryInterface); // bool(true) $userFactory = $app->get(UserFactoryInterface::class); // Address: $addressRepository = $app->get(AddressRepositoryInterface::class); // var_dump($addressRepository instanceof RepositoryInterface); // bool(true) $addressFactory = $app->get(AddressFactoryInterface::class); // Role: $roleRepository = $app->get(RoleRepositoryInterface::class); // var_dump($roleRepository instanceof RepositoryInterface); // bool(true) $roleFactory = $app->get(RoleFactoryInterface::class); // Run the app: $app->run();
您可以查看仓库接口以了解更多信息。
用户认证和认证器
以下认证和认证器接口根据默认的app/config/user.php
配置文件定义可用。
use Tobento\App\AppFactory; use Tobento\App\User\Authentication\AuthInterface; use Tobento\App\User\Authentication\Token\TokenStoragesInterface; use Tobento\App\User\Authentication\Token\TokenStorageInterface; use Tobento\App\User\Authentication\Token\TokenTransportsInterface; use Tobento\App\User\Authentication\Token\TokenTransportInterface; use Tobento\App\User\Authenticator\TokenAuthenticatorInterface; use Tobento\App\User\Authenticator\UserVerifierInterface; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Auth: $auth = $app->get(AuthInterface::class); // Token storages: $tokenStorages = $app->get(TokenStoragesInterface::class); // Default token storage: $tokenStorage = $app->get(TokenStorageInterface::class); // Token transports: $tokenTransports = $app->get(TokenTransportsInterface::class); // Default token transport: $tokenTransport = $app->get(TokenTransportInterface::class); // Default token authenticator: $tokenAuthenticator = $app->get(TokenAuthenticatorInterface::class); // Default user verifier: $userVerifier = $app->get(UserVerifierInterface::class); // Run the app: $app->run();
检索当前用户
在app/config/user.php
配置文件中定义的Tobento\App\User\Middleware\User::class
处理过程之后,将通过ServerRequestInterface::class
提供当前用户。
use Tobento\App\AppFactory; use Tobento\App\User\UserInterface; use Tobento\Service\User\UserInterface as ServiceUserInterface; use Tobento\Service\Acl\Authorizable; use Psr\Http\Message\ServerRequestInterface; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Routes: $app->route('GET', 'user', function(ServerRequestInterface $request) { // Get the current user (may be authenticated or not): $user = $request->getAttribute(UserInterface::class); // var_dump($user instanceof UserInterface); // bool(true) // var_dump($user instanceof ServiceUserInterface); // bool(true) // var_dump($user instanceof Authorizable); // bool(true) // Check if authenticated: $user->isAuthenticated(); // bool(false) return $user?->toArray(); }); // Run the app: $app->run();
检索认证用户
在app/config/user.php
配置文件中定义的Tobento\App\User\Middleware\Authentication::class
处理过程之后,将通过ServerRequestInterface::class
提供认证用户。
use Tobento\App\AppFactory; use Tobento\App\User\UserInterface; use Tobento\App\User\Authentication\AuthInterface; use Tobento\App\User\Authentication\AuthenticatedInterface; use Tobento\App\User\Authentication\Token\TokenInterface; use Tobento\Service\User\UserInterface as ServiceUserInterface; use Tobento\Service\Acl\Authorizable; use Psr\Http\Message\ServerRequestInterface; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Routes: $app->route('GET', 'user', function(ServerRequestInterface $request) { // Get the auth: $auth = $request->getAttribute(AuthInterface::class); // var_dump($auth instanceof AuthInterface); // bool(true) // You may check if user is authenticated: if ($auth->hasAuthenticated()) { return null; } // Get authenticated: $authenticated = $auth->getAuthenticated(); // var_dump($auth->getAuthenticated() instanceof AuthenticatedInterface); // bool(true) // Get the authenticated user: $user = $authenticated->user(); // var_dump($user instanceof UserInterface); // bool(true) // var_dump($user instanceof ServiceUserInterface); // bool(true) // var_dump($user instanceof Authorizable); // bool(true) // Get the authenticated token: $token = $authenticated->token(); // var_dump($token instanceof TokenInterface); // bool(true) // Get the authenticated via: // var_dump($authenticated->via()); // string(9) "loginlink" // Get the authenticated by (authenticator class name): // var_dump($authenticated->by()); // string(52) "Tobento\App\User\Authenticator\IdentityAuthenticator" return $user; }); // Run the app: $app->run();
认证用户
根据您的配置文件 app/config/user.php
的配置,有多种方法可以对用户进行认证。
通用认证流程
use Tobento\App\User\UserRepositoryInterface; use Tobento\App\User\Authentication\AuthInterface; use Tobento\App\User\Authentication\Authenticated; use Tobento\App\User\Authentication\Token\TokenStorageInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; class AuthController { public function authenticate( ServerRequestInterface $request, UserRepositoryInterface $userRepository, AuthInterface $auth, TokenStorageInterface $tokenStorage, ): ResponseInterface { // You may check if user is authenticated: // Or use the \Tobento\App\User\Middleware\Unauthenticated::class to do so. if ($auth->hasAuthenticated()) { // create and return response if is already authenticated. } // authenticate user manually: $user = $userRepository->findById(1); if (is_null($user)) { // create and return response if user does not exist. } // create token and start auth: $token = $tokenStorage->createToken( // Set the payload: payload: ['userId' => $user->id(), 'passwordHash' => $user->password()], // Set the name of which the user was authenticated via: authenticatedVia: 'login.auth', // The name is up to you. // Set the name of which the user was authenticated by (authenticator name) or null if none: authenticatedBy: null, // Set the point in time the token has been issued or null (now): issuedAt: new \DateTimeImmutable('now'), // Set the point in time after which the token MUST be considered expired or null: // The time might depend on the token storage e.g. session expiration! expiresAt: new \DateTimeImmutable('now +10 minutes'), ); $auth->start(new Authenticated(token: $token, user: $user)); // create and return response: return $createdResponse; } }
您可以使用一个 认证器 来对用户进行认证
例如,请参阅 身份认证器 - 登录控制器示例。
注销用户
要注销用户,请从 AuthInterface::class
调用 close
方法
use Tobento\App\User\Authentication\AuthInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; class AuthController { public function unauthenticate( AuthInterface $auth, ): ResponseInterface { $auth->close(); // create and return response: return $createdResponse; } }
Acl启动
acl 启动器执行以下操作
- 实现 acl 接口并设置角色
用户启动器 会自动启动 acl 启动器!
use Tobento\App\AppFactory; use Tobento\Service\Acl\AclInterface; // Create the app $app = (new AppFactory())->createApp(); // Adding boots // The acl boot gets booted automatically by the user boot: // $app->boot(\Tobento\App\User\Boot\Acl::class); $app->boot(\Tobento\App\User\Boot\User::class); $acl = $app->get(AclInterface::class); // Run the app $app->run();
您可以通过查看 Acl 服务 来了解更多信息。
添加角色
acl 启动器会添加从 Tobento\App\User\RoleRepositoryInterface::class
中找到的所有角色。您可以通过以下方式添加角色
使用迁移
use Tobento\Service\Migration\MigrationInterface; use Tobento\Service\Migration\ActionsInterface; use Tobento\Service\Migration\Actions; use Tobento\Service\Repository\Storage\Migration\RepositoryAction; use Tobento\Service\Repository\Storage\Migration\RepositoryDeleteAction; use Tobento\App\User\RoleRepositoryInterface; class RolesStorageMigration implements MigrationInterface { public function __construct( protected RoleRepositoryInterface $roleRepository, ) {} /** * Return a description of the migration. * * @return string */ public function description(): string { return 'User roles.'; } /** * Return the actions to be processed on install. * * @return ActionsInterface */ public function install(): ActionsInterface { return new Actions( RepositoryAction::newOrNull( repository: $this->roleRepository, description: 'Default roles', items: [ ['key' => 'guest', 'areas' => ['frontend'], 'active' => true], ['key' => 'registered', 'areas' => ['frontend'], 'active' => true], ['key' => 'business', 'areas' => ['frontend'], 'active' => true], ['key' => 'developer', 'areas' => ['backend'], 'active' => true], ['key' => 'administrator', 'areas' => ['backend'], 'active' => true], ['key' => 'editor', 'areas' => ['backend'], 'active' => true], ], ), ); } /** * Return the actions to be processed on uninstall. * * @return ActionsInterface */ public function uninstall(): ActionsInterface { return new Actions(); } }
然后使用应用程序迁移安装 迁移。
使用内存存储
您可以使用内存存储代替 app/config/user.php
配置文件中定义的默认存储
//... User\RoleRepositoryInterface::class => static function(ContainerInterface $c) { $storage = new InMemoryStorage([ 'roles' => [ 1 => [ 'key' => 'guest', 'areas' => ['frontend'], 'active' => true, ], ], ]); return new User\RoleStorageRepository( storage: $storage, table: 'roles', entityFactory: $c->get(User\RoleFactoryInterface::class), ); }, //...
您可以通过 验证角色中间件 验证角色。
添加规则
默认情况下,不会添加任何规则。您可以通过以下方式添加规则
使用应用程序
use Tobento\App\AppFactory; use Tobento\Service\Acl\AclInterface; // Create the app $app = (new AppFactory())->createApp(); // Adding boots // The acl boot gets booted automatically by // the user boot: // $app->boot(\Tobento\App\User\Boot\Acl::class); $app->boot(\Tobento\App\User\Boot\User::class); $acl = $app->get(AclInterface::class); $acl->rule('articles.read') ->title('Article Read') ->description('If a user can read articles'); // Run the app $app->run();
使用 acl 启动器
use Tobento\App\Boot; use Tobento\App\User\Boot\Acl; class AnyServiceBoot extends Boot { public const BOOT = [ // you may ensure the acl boot. Acl::class, ]; public function boot(Acl $acl) { $acl->rule('articles.read') ->title('Article Read') ->description('If a user can read articles'); } }
将通过 验证权限中间件 进行权限验证。
请参阅 规则 了解更多信息。
通过权限授权用户
有几种方法可以通过检查其权限来授权用户访问资源。
您可以通过查看 Acl - Permissions 部分来了解更多信息。
使用 acl
use Tobento\Service\Acl\AclInterface; class ArticleController { public function index(AclInterface $acl): string { if ($acl->cant('articles.read')) { return 'can not read'; } return 'can read'; } }
使用用户
use Tobento\App\User\UserInterface; use Psr\Http\Message\ServerRequestInterface; class ArticleController { public function index(ServerRequestInterface $request): string { $user = $request->getAttribute(UserInterface::class); if ($user->cant('articles.read')) { return 'can not read'; } return 'can read'; } }
使用中间件
使用 验证权限中间件 在路由上验证权限。
通过角色授权用户
使用 acl
use Tobento\Service\Acl\AclInterface; class ArticleController { public function index(AclInterface $acl): string { $user = $this->acl->getCurrentUser(); if ( $user && $user->hasRole() && $user->role()->key() !== 'editor' ) { return 'can not read'; } // or if ($user?->getRoleKey() !== 'editor') { return 'can not read'; } return 'can read'; } }
使用用户
use Tobento\App\User\UserInterface; use Psr\Http\Message\ServerRequestInterface; class ArticleController { public function index(ServerRequestInterface $request): string { $user = $request->getAttribute(UserInterface::class); if ($user->hasRole() && $user->role()->key() !== 'editor') { return 'can not read'; } // or if ($user->getRoleKey() !== 'editor') { return 'can not read'; } return 'can read'; } }
使用中间件
使用 验证角色中间件 在路由上验证角色。
Http用户错误处理启动
HTTP 用户错误处理器启动器处理任何特定于用户的异常,例如
Tobento\App\User\Exception\TokenExpiredException
Tobento\App\User\Exception\AuthenticationException
Tobento\App\User\Exception\AuthorizationException
Tobento\App\User\Exception\PermissionDeniedException
Tobento\App\User\Exception\RoleDeniedException
添加启动器
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\User\Boot\HttpUserErrorHandler::class); $app->boot(\Tobento\App\User\Boot\User::class); // Run the app $app->run();
您可以创建一个自定义的 错误处理器 或添加一个具有更高优先级的 错误处理器,其优先级为 3000
,如 Tobento\App\User\Boot\HttpUserErrorHandler::class
中定义。
中间件
用户中间件
Tobento\App\User\Middleware\User::class
中间件将确保请求属性中始终有一个用户可用。此外,它还设置当前 acl 用户。
中间件处理方法的代码片段
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // Get the authenticated user if exists: $user = $request->getAttribute(AuthInterface::class)?->getAuthenticated()?->user(); // Otherwise, create guest user: if (is_null($user)) { $user = $this->userFactory->createGuestUser(); } $request = $request->withAttribute(UserInterface::class, $user); // Set user on acl: if ($this->acl) { $this->acl->setCurrentUser($user); } return $handler->handle($request); }
认证中间件
Tobento\App\User\Middleware\Authentication::class
中间件将尝试在每个请求中通过使用定义的令牌传输来从请求中获取令牌,然后由令牌认证器进行认证。
中间件处理方法的代码片段
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // Add auth to the request: $request = $request->withAttribute(AuthInterface::class, $this->auth); // Handle token: $tokenId = $this->tokenTransport->fetchTokenId($request); if (!is_null($tokenId)) { try { $token = $this->tokenStorage->fetchToken($tokenId); $user = $this->tokenAuthenticator->authenticate($token); $this->auth->start( new Authenticated(token: $token, user: $user), $this->tokenTransport->name() ); } catch (TokenNotFoundException $e) { // ignore TokenNotFoundException as to // proceed with handling the response. // other exceptions will be handled by the error handler! } } // Handle the response: $response = $handler->handle($request); if (! $this->auth->hasAuthenticated()) { return $response; } if ($this->auth->isClosed()) { $this->tokenStorage->deleteToken($this->auth->getAuthenticated()->token()); return $this->tokenTransport->removeToken( token: $this->auth->getAuthenticated()->token(), request: $request, response: $response, ); } return $this->tokenTransport->commitToken( token: $this->auth->getAuthenticated()->token(), request: $request, response: $response, ); }
使用中间件进行认证
请参阅 按路由不同认证 部分来了解更多关于此中间件的信息。
认证中间件
Authenticated::class
中间件保护路由免受未经认证的用户访问。
use Tobento\App\AppFactory; use Tobento\App\User\Middleware\Authenticated; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Routes: $app->route('GET', 'account', function() { // only for authenticated user! return 'response'; })->middleware(Authenticated::class)->name('account'); $app->route('GET', 'account', function() { // only for authenticated user! return 'response'; })->middleware([ Authenticated::class, // you may allow access only to user authenticated via: 'via' => 'loginform|loginlink', // or you may allow access only to user authenticated except via: 'exceptVia' => 'remembered|loginlink', // you may specify a custom message to show to the user: 'message' => 'You have insufficient rights to access the resource!', // you may specify a message level: 'messageLevel' => 'notice', // you may specify a route name for redirection: 'redirectRoute' => 'login', // or you may specify an uri for redirection 'redirectUri' => '/login', ]); // you may use the middleware alias defined in user config: $app->route('GET', 'account', function() { return 'response'; })->middleware(['auth', 'via' => 'loginform|loginlink']); // Run the app: $app->run();
未认证中间件
Unauthenticated::class
中间件保护路由免受已认证用户的访问。
use Tobento\App\AppFactory; use Tobento\App\User\Middleware\Unauthenticated; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Routes: $app->route('GET', 'login', function() { // only for unauthenticated user! return 'response'; })->middleware(Unauthenticated::class)->name('login'); $app->route('GET', 'login', function() { // only for unauthenticated user! return 'response'; })->middleware([ Unauthenticated::class, // you may allow access only to user authenticated via: 'via' => 'remembered|loginlink', // or you may allow access only to user authenticated except via: 'exceptVia' => 'remembered|loginlink', // you may specify a custom message to show to the user: 'message' => 'Already authenticated!', // you may specify a message level: 'messageLevel' => 'notice', // you may specify a route name for redirection: 'redirectRoute' => 'home', // or you may specify an uri for redirection 'redirectUri' => '/home', ]); // you may use the middleware alias defined in user config: $app->route('GET', 'login', function() { return 'response'; })->middleware(['guest', 'message' => 'Already authenticated!']); // Run the app: $app->run();
验证中间件
Verified::class
中间件保护路由免受未经验证的用户的访问。
use Tobento\App\AppFactory; use Tobento\App\User\Middleware\Verified; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Routes: $app->route('GET', 'account', function() { // only for users with at least one channel verified! return 'response'; })->middleware(Verified::class)->name('account'); $app->route('GET', 'account', function() { // only for users with specific channels verified! return 'response'; })->middleware([ Verified::class, // specify the channels the user must have at least verified one of: 'oneOf' => 'email|smartphone', // OR specify the channels the user must have verified all: 'allOf' => 'email|smartphone', // you may specify a custom message to show to the user: 'message' => 'You are not verified to access the resource!', // you may specify a message level: 'messageLevel' => 'notice', // you may specify a route name for redirection: 'redirectRoute' => 'login', // or you may specify an uri for redirection 'redirectUri' => '/login', ]); // you may use the middleware alias defined in user config: $app->route('GET', 'account', function() { return 'response'; })->middleware(['verified', 'oneOf' => 'email|smartphone']); // Run the app: $app->run();
查看用户渠道验证部分,了解更多信息。
验证权限中间件
VerifyPermission::class
中间件保护路由免受没有定义的权限的用户访问。如果用户权限不足,将抛出Tobento\App\User\Exception\PermissionDeniedException::class
异常。
use Tobento\App\AppFactory; use Tobento\App\User\Middleware\VerifyPermission; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Routes: $app->route('GET', 'login', function() { // only for user with permission login.show! return 'response'; })->middleware(VerifyPermission::class)->name('login.show'); // you may specify the following parameters: $app->route('GET', 'login', function() { return 'response'; })->middleware([ VerifyPermission::class, // set the permission (optional). // if not set it uses the route name: 'permission' => 'login.show|anotherPermission', // you may specify a custom message to show to the user: 'message' => 'You do not have permission to access the resource!', // you may specify a message level: 'messageLevel' => 'notice', // you may specify a route name for redirection: 'redirectRoute' => 'home', // or you may specify an uri for redirection 'redirectUri' => '/home', ])->name('login.show'); // you may use the middleware alias defined in user config: $app->route('GET', 'login', function() { return 'response'; })->middleware('can'); // Run the app: $app->run();
查看如何处理异常的Http User 错误处理器引导。
验证路由权限中间件
VerifyRoutePermission::class
中间件保护路由免受没有定义的权限的用户访问。如果用户权限不足,将抛出Tobento\App\User\Exception\PermissionDeniedException::class
异常。
use Tobento\App\AppFactory; use Tobento\App\User\Middleware\VerifyRoutePermission; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Routes: $app->routeResource('roles', RolesController::class)) ->middleware([ VerifyRoutePermission::class, 'permissions' => [ // 'route.name' => 'permission' 'roles.index' => 'roles', 'roles.show' => 'roles', 'roles.create' => 'roles.create', 'roles.store' => 'roles.create', 'roles.edit' => 'roles.edit', 'roles.update' => 'roles.edit', 'roles.delete' => 'roles.delete', ], // you may specify a custom message to show to the user: 'message' => 'You do not have permission to access the resource!', // you may specify a message level: 'messageLevel' => 'notice', // you may specify a route name for redirection: 'redirectRoute' => 'home', // or you may specify an uri for redirection 'redirectUri' => '/home', ]); // Run the app: $app->run();
查看如何处理异常的Http User 错误处理器引导。
验证角色中间件
VerifyRole::class
中间件保护路由免受没有定义的角色用户访问。如果用户角色不足,将抛出Tobento\App\User\Exception\RoleDeniedException::class
异常。
use Tobento\App\AppFactory; use Tobento\App\User\Middleware\VerifyRole; $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/../app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'public', 'public') ->dir($app->dir('root').'vendor', 'vendor'); // Adding boots: $app->boot(\Tobento\App\Http\Boot\Routing::class); $app->boot(\Tobento\App\User\Boot\User::class); $app->booting(); // Routes: $app->route('GET', 'login', function() { // only for user with role editor and administrator! return 'response'; })->middleware([ VerifyRole::class, // set the role. 'role' => 'editor|administrator', // you may specify a custom message to show to the user: 'message' => 'You do not have a required role to access the resource!', // you may specify a message level: 'messageLevel' => 'notice', // you may specify a route name for redirection: 'redirectRoute' => 'home', // or you may specify an uri for redirection 'redirectUri' => '/home', ]); // you may use the middleware alias defined in user config: $app->route('GET', 'login', function() { return 'response'; })->middleware(['role', 'role' => 'editor|administrator']); // Run the app: $app->run();
查看如何处理异常的Http User 错误处理器引导。
认证器
身份认证器
Tobento\App\User\Authenticator\IdentityAuthenticator::class
通过请求输入的user
中的email
和/或username
和/或smartphone
来识别用户。此外,它还会验证请求输入的password
。您可以指定哪些属性可以用于识别,以及是否要验证密码。
登录控制器示例
use Tobento\App\User\Authenticator\IdentityAuthenticator; use Tobento\App\User\Authenticator\UserVerifiers; use Tobento\App\User\Authenticator\UserRoleAreaVerifier; use Tobento\App\User\Authentication\AuthInterface; use Tobento\App\User\Authentication\Authenticated; use Tobento\App\User\Authentication\Token\TokenStorageInterface; use Tobento\App\User\Exception\AuthenticationException; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; class LoginController { public function login( ServerRequestInterface $request, IdentityAuthenticator $authenticator, AuthInterface $auth, TokenStorageInterface $tokenStorage, ): ResponseInterface { // You may specify the identity attributes to be checked. // At least one attribute is required. $authenticator->identifyBy(['email', 'username', 'smartphone', 'password']); // or only identify by email and verify password. $authenticator->identifyBy(['email', 'password']); // you may verify user attributes: $authenticator = $authenticator->withUserVerifier( new UserVerifiers( new UserRoleAreaVerifier('frontend'), ), ); // You may change the request input names: $authenticator->userInputName('user'); $authenticator->passwordInputName('password'); // You may change the request method, // only 'POST' (default), 'GET' and 'PUT': $authenticator->requestMethod('POST'); // try to authenticate user: try { $user = $authenticator->authenticate($request); } catch (AuthenticationException $e) { // handle exception: // create and return response for exception: return $createdResponse; } // on success create token and start auth: $token = $tokenStorage->createToken( payload: ['userId' => $user->id(), 'passwordHash' => $user->password()], authenticatedVia: 'loginform', authenticatedBy: $authenticator::class, // issuedAt: $issuedAt, // expiresAt: $expiresAt, ); $auth->start(new Authenticated(token: $token, user: $user)); // create and return response: return $createdResponse; } }
属性认证器
Tobento\App\User\Authenticator\AttributesAuthenticator::class
通过指定的用户属性来识别用户。与Identity Authenticator不同,此认证器通过所有属性来识别用户。
登录控制器示例
use Tobento\App\User\Authenticator\AttributesAuthenticator; use Tobento\App\User\Authenticator\UserVerifiers; use Tobento\App\User\Authenticator\UserRoleAreaVerifier; use Tobento\App\User\Authentication\AuthInterface; use Tobento\App\User\Authentication\Authenticated; use Tobento\App\User\Authentication\Token\TokenStorageInterface; use Tobento\App\User\Exception\AuthenticationException; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; class LoginController { public function login( ServerRequestInterface $request, AttributesAuthenticator $authenticator, AuthInterface $auth, TokenStorageInterface $tokenStorage, ): ResponseInterface { // Specify the user attributes to be identified: $authenticator->addAttribute( // specify the user attribute name: name: 'email', // specify the reuest input name: // optional, if not set name will be used! inputName: 'email', // you may specify the validation rules: // default rule is required|string|minLen:3|maxLen:150 validate: 'required|email', ); //$authenticator->addAttribute(name: 'smartphone'); // will verify password. $authenticator->addAttribute(name: 'password'); // you may verify user attributes: $authenticator = $authenticator->withUserVerifier( new UserVerifiers( new UserRoleAreaVerifier('frontend'), ), ); // You may change the request method, // only 'POST' (default), 'GET' and 'PUT': $authenticator->requestMethod('POST'); // try to authenticate user: try { $user = $authenticator->authenticate($request); } catch (AuthenticationValidationException $e) { // you may handle specific exception: // create and return response for exception: return $createdResponse; } catch (AuthenticationException $e) { // handle exception: // create and return response for exception: return $createdResponse; } // on success create token and start auth: $token = $tokenStorage->createToken( payload: ['userId' => $user->id(), 'passwordHash' => $user->password()], authenticatedVia: 'loginform', authenticatedBy: $authenticator::class, // issuedAt: $issuedAt, // expiresAt: $expiresAt, ); $auth->start(new Authenticated(token: $token, user: $user)); // create and return response: return $createdResponse; // create and return response: return $createdResponse; } }
用户验证器
用户验证器可以在验证用户时用于验证某些用户属性。例如,请参阅Identity Authenticator。
用户权限验证器
use Tobento\App\User\Authenticator\UserPermissionVerifier; // User must have one the specified permission. $verifier = new UserPermissionVerifier('permission');
用户角色验证器
use Tobento\App\User\Authenticator\UserRoleVerifier; // User must have one of the specified role. $verifier = new UserRoleVerifier('editor', 'author');
用户角色区域验证器
use Tobento\App\User\Authenticator\UserRoleAreaVerifier; // User must have one of the specified role area. $verifier = new UserRoleAreaVerifier('frontend', 'api');
令牌认证器
令牌认证器负责根据令牌认证用户。有关更多详细信息,请参阅Authentication Middleware。
您可以在app/config/user.php
文件中添加令牌验证器以验证某些令牌有效负载属性。
use Tobento\App\User\Authentication; use Psr\Container\ContainerInterface; return [ // ... 'interfaces' => [ // ... // Default token authenticator: // Authenticator\TokenAuthenticatorInterface::class => Authenticator\TokenAuthenticator::class, // Example with token verifiers: Authenticator\TokenAuthenticatorInterface::class => static function(ContainerInterface $c) { return new Authenticator\TokenAuthenticator( verifier: new Authenticator\TokenVerifiers( new Authenticator\TokenPasswordHashVerifier( // The token issuers (storage names) to verify password hash. // If empty it gets verified for all issuers. issuers: ['session'], // The attribute name of the payload: name: 'passwordHash', ), ), ); }, // ... ], ];
令牌验证器
在通过令牌验证用户时,可以使用令牌验证器验证某些令牌有效负载属性。例如,请参阅Token Authenticator。
令牌密码散列验证器
TokenPasswordHashVerifier::class
可用于在用户更改密码时使令牌失效。
use Tobento\App\User\Authenticator\TokenPasswordHashVerifier; $verifier = new TokenPasswordHashVerifier( // The token issuers (storage names) to verify password hash. // If empty it gets verified for all issuers. issuers: ['session'], // Will only be verified if authenticated // via remembered or loginlink if specified: authenticatedVia: 'remembered|loginlink', // The attribute name of the payload: name: 'passwordHash', );
令牌负载验证器
TokenPayloadVerifier::class
可用于在指定的有效负载属性与给定值不匹配时使令牌失效。
use Tobento\App\User\Authenticator\TokenPayloadVerifier; $verifier = new TokenPayloadVerifier( // Specify the payload attribute name: name: 'remoteAddress', // Specify the value to match: value: $_SERVER['REMOTE_ADDR'] ?? null, // The token issuers (storage names) to verify password hash. // If empty it gets verified for all issuers. issuers: ['session'], // Will only be verified if authenticated // via remembered or loginlink if specified: authenticatedVia: 'remembered|loginlink', );
令牌存储
空存储
NullStorage
完全不存储任何令牌。这意味着您永远不会被认证。
在app/config/user.php
文件中
use Tobento\App\User\Authentication; use Psr\Container\ContainerInterface; return [ // ... 'interfaces' => [ // ... // Define the token storages you wish to support: Authentication\Token\TokenStoragesInterface::class => static function(ContainerInterface $c) { return new Authentication\Token\TokenStorages( // add null storage: new Authentication\Token\NullStorage(), ); }, // Define the default token storage used for auth: Authentication\Token\TokenStorageInterface::class => static function(ContainerInterface $c) { // you might change to null: return $c->get(Authentication\Token\TokenStoragesInterface::class)->get('null'); }, // ... ], ];
内存存储
InMemoryStorage
仅将令牌存储在内存中。
在app/config/user.php
文件中
use Tobento\App\User\Authentication; use Psr\Container\ContainerInterface; use Psr\Clock\ClockInterface; return [ // ... 'interfaces' => [ // ... // Define the token storages you wish to support: Authentication\Token\TokenStoragesInterface::class => static function(ContainerInterface $c) { return new Authentication\Token\TokenStorages( // add inmemory storage: new Authentication\Token\InMemoryStorage( clock: $c->get(ClockInterface::class), ), ); }, // Define the default token storage used for auth: Authentication\Token\TokenStorageInterface::class => static function(ContainerInterface $c) { // you might change to inmemory: return $c->get(Authentication\Token\TokenStoragesInterface::class)->get('inmemory'); }, // ... ], ];
仓库存储
RepositoryStorage
使用Service Repository Storage存储令牌。
在app/config/user.php
文件中
use Tobento\App\User\Authentication; use Tobento\Service\Storage\StorageInterface; use Psr\Container\ContainerInterface; use Psr\Clock\ClockInterface; return [ // ... 'interfaces' => [ // ... // Define the token storages you wish to support: Authentication\Token\TokenStoragesInterface::class => static function(ContainerInterface $c) { return new Authentication\Token\TokenStorages( new Authentication\Token\RepositoryStorage( clock: $c->get(ClockInterface::class), repository: new Authentication\Token\TokenRepository( storage: $c->get(StorageInterface::class)->new(), table: 'auth_tokens', ), name: 'repository', ), ); }, // Define the default token storage used for auth: Authentication\Token\TokenStorageInterface::class => static function(ContainerInterface $c) { return $c->get(Authentication\Token\TokenStoragesInterface::class)->get('repository'); }, // ... ], ];
删除过期令牌
您可以通过调用deleteExpiredTokens
方法来删除所有过期令牌。
$repositoryStorage->deleteExpiredTokens();
会话存储
在PHP会话中存储令牌。
在app/config/user.php
文件中
use Tobento\App\User\Authentication; use Tobento\Service\Session\SessionInterface; use Psr\Container\ContainerInterface; use Psr\Clock\ClockInterface; return [ // ... 'interfaces' => [ // ... // Define the token storages you wish to support: Authentication\Token\TokenStoragesInterface::class => static function(ContainerInterface $c) { return new Authentication\Token\TokenStorages( // add session storage: new Authentication\Token\SessionStorage( session: $c->get(SessionInterface::class), clock: $c->get(ClockInterface::class), ), ); }, // Define the default token storage used for auth: Authentication\Token\TokenStorageInterface::class => static function(ContainerInterface $c) { // you might change to session: return $c->get(Authentication\Token\TokenStoragesInterface::class)->get('session'); }, // ... ], ];
确保在您的应用程序中引导App Http - Session Boot。
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\User\Boot\User::class); $app->boot(\Tobento\App\Http\Boot\Session::class); // Run the app $app->run();
令牌传输
Cookie传输
将认证令牌存储在cookie中。
在app/config/user.php
文件中
use Tobento\App\User\Authentication; use Psr\Container\ContainerInterface; use Psr\Clock\ClockInterface; return [ // ... 'interfaces' => [ // ... // Define the token transport you wish to support: Authentication\Token\TokenTransportsInterface::class => static function(ContainerInterface $c) { return new Authentication\Token\TokenTransports( new Authentication\Token\CookieTransport( clock: $c->get(ClockInterface::class), cookieName: 'token', ), ); }, // Define the default token transport(s) used for auth: Authentication\Token\TokenTransportInterface::class => static function(ContainerInterface $c) { return $c->get(Authentication\Token\TokenTransportsInterface::class)->get('cookie'); //return $c->get(Authentication\Token\TokenTransportsInterface::class); // all //return $c->get(Authentication\Token\TokenTransportsInterface::class)->only(['cookie']); }, // ... ], ];
确保在您的应用程序中引导App Http - Cookies Boot。
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Adding boots $app->boot(\Tobento\App\User\Boot\User::class); $app->boot(\Tobento\App\Http\Boot\Cookies::class); // Run the app $app->run();
Header传输
将认证令牌存储在HTTP头中。
在app/config/user.php
文件中
use Tobento\App\User\Authentication; use Psr\Container\ContainerInterface; return [ // ... 'interfaces' => [ // ... // Define the token transport you wish to support: Authentication\Token\TokenTransportsInterface::class => static function(ContainerInterface $c) { return new Authentication\Token\TokenTransports( new Authentication\Token\HeaderTransport(name: 'header', headerName: 'X-Auth-Token'), ); }, // Define the default token transport(s) used for auth: Authentication\Token\TokenTransportInterface::class => static function(ContainerInterface $c) { return $c->get(Authentication\Token\TokenTransportsInterface::class)->get('header'); //return $c->get(Authentication\Token\TokenTransportsInterface::class); // all //return $c->get(Authentication\Token\TokenTransportsInterface::class)->only(['header']); }, // ... ], ];
事件
可用事件
use Tobento\App\User\Event;
支持事件
简单安装App Event包。
控制台
在运行命令之前,您需要安装App Console包。
Acl角色命令
列出所有acl角色
php ap acl:roles
Acl规则命令
列出所有acl规则
php ap acl:rules
删除过期令牌命令
您可以从支持它的令牌存储中删除过期令牌。
php ap auth:purge-tokens
您只能从特定的令牌存储中删除。
php ap auth:purge-tokens --storage=name
迁移
角色权限动作
您可以使用 RolePermissionsAction::class
添加和移除角色的权限。
use Tobento\App\User\Migration\RolePermissionsAction; use Tobento\App\User\RoleRepositoryInterface; use Tobento\Service\Migration\MigrationInterface; use Tobento\Service\Migration\ActionsInterface; use Tobento\Service\Migration\Actions; class RolesPermissionMigration implements MigrationInterface { public function __construct( protected RoleRepositoryInterface $roleRepository, ) {} /** * Return a description of the migration. * * @return string */ public function description(): string { return 'Role permissions.'; } /** * Return the actions to be processed on install. * * @return ActionsInterface */ public function install(): ActionsInterface { return new Actions( new RolePermissionsAction( roleRepository: $this->roleRepository, add: [ 'developer' => ['roles', 'roles.create', 'roles.edit', 'roles.delete', 'roles.permissions'], 'administrator' => ['roles', 'roles.create', 'roles.edit', 'roles.delete', 'roles.permissions'], ], description: 'Roles permissions added for developer and administrator', ), ); } /** * Return the actions to be processed on uninstall. * * @return ActionsInterface */ public function uninstall(): ActionsInterface { return new Actions( new RolePermissionsAction( roleRepository: $this->roleRepository, remove: [ 'developer' => ['roles', 'roles.create', 'roles.edit', 'roles.delete', 'roles.permissions'], 'administrator' => ['roles', 'roles.create', 'roles.edit', 'roles.delete', 'roles.permissions'], ], description: 'Roles permissions removed for developer and administrator', ), ); } }
然后使用应用程序迁移安装 迁移。
了解更多
不同路由的认证方式
首先,配置 app/config/user.php
文件并定义您希望使用的令牌存储和传输方式。
use Tobento\App\User\Authentication; use Psr\Container\ContainerInterface; use Psr\Clock\ClockInterface; return [ // ... 'middlewares' => [ // Uncomment it and set it on each route individually! // User\Middleware\Authentication::class, ], 'interfaces' => [ // ... // Define the token storages you wish to support: Authentication\Token\TokenStoragesInterface::class => static function(ContainerInterface $c) { return new Authentication\Token\TokenStorages( new Authentication\Token\SessionStorage( session: $c->get(SessionInterface::class), clock: $c->get(ClockInterface::class), ), ); }, // Define the token transport you wish to support: Authentication\Token\TokenTransportsInterface::class => static function(ContainerInterface $c) { return new Authentication\Token\TokenTransports( new Authentication\Token\CookieTransport( clock: $c->get(ClockInterface::class), cookieName: 'token', ), new Authentication\Token\HeaderTransport(name: 'header', headerName: 'X-Auth-Token'), ); }, // ... ], ];
最后,将中间件添加到您的路由中。
use Tobento\App\User\Middleware\AuthenticationWith; use Tobento\Service\Routing\RouteGroupInterface; // ... // Routes: // Web example: $app->routeGroup('', function(RouteGroupInterface $group) { // define your web routes: })->middleware([ AuthenticationWith::class, // specify the token transport name: 'transportName' => 'cookie', // specify the token storage name: 'storageName' => 'session', ]); // Api example: $app->routeGroup('api', function(RouteGroupInterface $group) { // define your api routes: })->middleware([ AuthenticationWith::class, // specify the token transport name: 'transportName' => 'header', // specify the token storage name: 'storageName' => 'session', ]); // ...
另一种方法是使用 Http - Area Boot,为每个运行在自身应用程序中的区域 "web" 和 "api"。
密码散列
使用在 app/config/user.php
文件中定义的 PasswordHasherInterface::class
来散列和验证用户密码。
基本用法
use Tobento\App\User\PasswordHasherInterface; class SomeService { public function __construct( private PasswordHasherInterface $passwordHasher, ) { // hash password: $hashedPassword = $passwordHasher->hash(plainPassword: 'password'); // verify password: $isValid = $passwordHasher->verify(hashedPassword: $hashedPassword, plainPassword: 'password'); } }
以下认证器使用密码散列器来验证密码。
用户频道验证
添加已验证的频道
use Tobento\App\User\UserRepositoryInterface; use DateTime; class SomeService { public function __construct( private UserRepositoryInterface $userRepository, ) { $updatedUser = $userRepository->addVerified( id: 5, // user id channel: 'email', verifiedAt: new DateTime('2023-09-24 00:00:00'), ); } }
移除已验证的频道
use Tobento\App\User\UserRepositoryInterface; use DateTime; class SomeService { public function __construct( private UserRepositoryInterface $userRepository, ) { $updatedUser = $userRepository->removeVerified( id: 5, // user id channel: 'email', ); } }
用户验证方法
use Tobento\App\User\UserInterface; var_dump($user instanceof UserInterface); // bool(true) // Get the verified channels: $verified = $user->getVerified(); // ['email' => '2023-09-24 00:00:00'] // Get the date verified at for a specific channel: $emailVerifiedAt = $user->getVerifiedAt(channel: 'email'); // '2023-09-24 00:00:00' or NULL // Returns true if the specified channels are verified, otherwise false. $verified = $user->isVerified(channels: ['email', 'smartphone']); // Returns true if at least one channel is verified, otherwise false. $verified = $user->isOneVerified(); // or one of the specified channels: $verified = $user->isOneVerified(channels: ['email', 'smartphone']);
应用用户包
您可以为您的应用程序使用以下用户包。
- App User Web - 登录、注册等。(即将推出)
- App User Manager - 用户、角色和权限的 CRUD 操作。(即将推出)
- App User Jwt - 通过 JSON Web Token 支持进行身份验证。(即将推出)
- App User Login Link - 通过登录链接进行身份验证。(即将推出)