ride/lib-security

Ride框架的安全库

1.3.0 2024-06-26 09:04 UTC

README

PHP Ride框架的安全抽象库。

该库实现基于角色的访问控制。更多关于此的信息请参阅 维基百科

本库包含的内容

SecurityModel

SecurityModel 接口是安全实现的数据源的代理。它提供用户、角色和权限。

User

User 接口表示一个用户,该用户可以向应用程序进行身份验证。您可以将角色附加到用户,以便授予他们对应用程序特定部分的访问权限。User 接口由安全模型实现。

Role

Role 接口表示一组允许通过授权权限和允许路径执行的操作。通过将角色附加到用户,您授予用户对应用程序特定部分的访问权限。Role 接口由安全模型实现。

Permission

Permission 接口用于授予或拒绝单个操作。

为了保护应用程序的某个部分,您应在应用程序代码中始终检查授予的权限。不要检查当前用户是否是特定用户,或者当前用户是否具有特定角色。这将违反安全模型的灵活性。

Permission 接口由安全模型实现。

Authenticator

Authenticator 接口决定身份验证机制并保持当前用户的会话状态。

GenericAuthenticator

GenericAuthenticator 提供默认或通用的身份验证器实现。

您可以启用唯一会话。当用户从另一个客户端登录时,此功能用于使原始客户端注销。

它还支持切换用户功能。

ChainAuthenticator

您可以使用 ChainAuthenticator 将不同的身份验证器链接在一起。同时提供不同的身份验证机制。

PathMatcher

PathMatcher 负责将路径正则表达式或规则与提供的路径和方法匹配。

GenericPathMatcher

GenericPathMatcher 提供默认或通用的路径匹配器实现。

您可以在规则中使用以下特殊标记

  • *: 匹配单个路径标记
  • **: 匹配所有内容
  • !: 在路径前缀加上感叹号以进行负(不是)匹配

您还可以在方括号内定义一个或多个方法。

所有规则都将进行检查,并按照提供的顺序执行。这对于 not 函数是必需的。

例如,假设以下规则

/admin**
/sites**
!/sites/my-site/pages/*/content [GET]

这些规则将匹配以 /admin/sites 开头的所有请求,除了对 my-site 每页内容的 GET 请求。

Voter

Voter 接口用于检查授予的权限和允许的路径。

ModelVoter

ModelVoter 通过安全模型执行其检查。它使用当前用户及其角色来获取授予的权限和允许的路径。

ChainVoter

ChainVoter 用于将不同的投票者链接在一起。这可以用于捕获特殊情况或某些边缘情况。

您有 3 种不同的策略

  • 肯定:此策略在任一投票者授予访问权限后立即授予访问权限。这是默认策略。
  • 共识:此策略在大多数投票者授予访问权限时授予访问权限。
  • 一致:此策略在所有投票者都授予访问权限时授予访问权限。

安全管理器

安全管理器类是此库的代理。它将其他组件粘合在一起,形成一个易于使用的接口。使用此类的实例来处理您的安全。

代码示例

查看此代码示例以了解此库的一些可能性。

注意:此示例中使用的某些类来自ride/lib-security-genericride/web-securityride/web-security-generic

<?php

use ride\library\encryption\hash\GenericHash;
use ride\library\event\EventManager;
use ride\library\http\Request;
use ride\library\security\authenticator\ChainAuthenticator;
use ride\library\security\authenticator\GenericAuthenticator;
use ride\library\security\exception\EmailAuthenticationException;
use ride\library\security\exception\InactiveAuthenticationException;
use ride\library\security\exception\PasswordAuthenticationException;
use ride\library\security\exception\UnauthorizedException;
use ride\library\security\exception\UsernameAuthenticationException;
use ride\library\security\exception\UserNotFoundException;
use ride\library\security\exception\UserSwitchException;
use ride\library\security\matcher\GenericPathMatcher;
use ride\library\security\model\generic\GenericSecurityModel;
use ride\library\security\model\ChainSecurityModel;
use ride\library\security\voter\ChainVoter;
use ride\library\security\voter\ModelVoter;
use ride\library\security\SecurityManager;
use ride\library\system\file\File;

use ride\web\security\authenticator\io\SessionAuthenticatorIO;
use ride\web\security\authenticator\HttpAuthenticator;

function createSecurityManager(EventManager $eventManager, File $fileSecurityModel) {
    // first create the default authenticator
    $sessionAuthenticatorIO = new SessionAuthenticatorIO(); // used to store values in the session
    $salt = 'a-random-string'; // salt for value generation
    $timeout = 1800; // time in seconds
    $isUnique = false; // allow only 1 client per user at the same time
    
    $genericAuthenticator = new GenericAuthenticator($sessionAuthenticatorIO, $salt, $timeout, $isUnique);
    
    // we use a chain so we can add other implementations like HTTP authentication or OAuth
    $chainAuthenticator = new ChainAuthenticator();
    $chainAuthenticator->addAuthenticator($genericAuthenticator);
    
    // let's add the HTTP authenticator to the chain (optional)
    $realm = 'My Site'; // the title of the login box
    $httpAuthenticator = new HttpAuthenticator($sessionAuthenticatorIO, $realm, $eventManager);
    
    $chainAuthenticator->addAuthenticator($httpAuthenticator);
    
    // decide the hash algorithm
    $hashAlgorithm = new GenericHash('sha512');
    
    // initialize the voter
    $genericPathMatcher = new GenericPathMatcher();
    
    $modelVoter = new ModelVoter($genericPathMatcher);
    
    // again a chain to add other voters if needed
    $chainVoter = new ChainVoter();
    $chainVoter->addVoter($modelVoter);
    
    // now, we create the security model
    // as example we use a file based security model which is good for a small user base.
    $xmlSecurityModelIO = new XmlSecurityModelIO($fileSecurityModel);
    $genericSecurityModel = new GenericSecurityModel($xmlSecurityModelIO, $eventManager, $hashAlgorithm);
    
    // a chain, you guessed it ...
    $chainSecurityModel = new ChainSecurityModel();
    $chainSecurityModel->addSecurityModel($genericSecurityModel);
    
    // throw it all together in the security manager
    $securityManager = new SecurityManager($chainAuthenticator, $eventManager);
    $securityManager->setHashAlgorithm($hashAlgorithm);
    $securityManager->setSecurityModel($chainSecurityModel);
    $securityManager->setVoter($chainVoter);
    
    return $securityManager;
}

function manageSecurityModel(SecurityManager $securityManager) {
    $securityModel = $securityManager->getSecurityModel();
    
    // set the globally secured paths 
    $securedPaths = array(
        '/admin**',
        '/sites**',
    );
    
    $securityModel->setSecuredPaths($securedPaths);
    
    // create some roles
    $administratorRole = $securityModel->createRole();
    $administratorRole->setName('Administrator');
    $administratorRole->setWeight(99);
    
    $contentManagerRole = $securityModel->createRole();
    $contentManagerRole->setName('Content Manager');
    $contentManagerRole->setWeight(50);
    
    $securityModel->saveRole($adminstratorRole);
    $securityModel->saveRole($contentManagerRole);
    
    // allow paths and grant permissions for the roles
    $securityModel->setAllowedPathsToRole($administratorRole, array('**'));
    $securityModel->setAllowedPathsToRole($contentManagerRole, array('/sites**'));
    
    $securityModel->setGrantedPermissionsToRole($administratorRole, array('security.switch'));
    $securityModel->setGrantedPermissionsToRole($contentManagerRole, array('security.switch'));
    
    // create users
    $administratorUser = $securityModel->createUser();
    $administratorUser->setUsername('admin');
    $administratorUser->setPassword('secure password');
    $administratorUser->setIsActive(true);
    
    $contentManagerUser = $securityModel->createUser();
    $contentManagerUser->setUsername('cm');
    $contentManagerUser->setPassword('secure password');
    $contentManagerUser->setIsActive(true);
    
    $securityModel->saveUser($administratorUser);
    $securityModel->saveUser($contentManagerUser);
    
    // assign roles to the users
    $securityModel->setRolesToUser($administratorUser, array($administratorRole));
    $securityModel->setRolesToUser($contentManagerUser, array($contentManagerRole));
    
    // create a super user
    $superUser = $securityModel->createUser();
    $superUser->setUsername('root');
    $superUser->setPassword('secure password');
    $superUser->setIsActive(true);
    $superUser->setIsSuperUser(true);
    
    $securityModel->saveUser($superUser);
    
    // create a regular user with all properties
    $regularUser = $securityModel->createUser();
    $regularUser->setDisplayName('John Doe');
    $regularUser->setUsername('john');
    $regularUser->setPassword('secure password');
    $regularUser->setEmail('john@doe.com');
    $regularUser->setIsEmailConfirmed(true);
    $regularUser->setImage('upload/users/john-doe-avatar.png');
    $regularUser->setPreference('locale', 'en_GB'); // any custom preference
    $regularUser->setIsActive(true);
    
    $securityModel->saveUser($regularUser);
    
    // delete it again
    $securityModel->deleteUser($regularUser);
    
    // find some users
    $user = $securityModel->getUserById(1);
    $user = $securityModel->getUserByUsername('admin');
    $user = $securityModel->getUserByEmail('john@doe.com');
    
    $options = array(
        // 'query' => 'adm',
        // 'username' => 'adm',
        // 'email' => 'adm',
        'page' => 1,
        'limit' => 20,
    ); 
    
    $users = $securityModel->getUsers($options);
    $numUsers = $securityModel->countUsers($options);
    
    // the same for roles
    $role = $securityModel->getRoleById(1);
    $role = $securityModel->getRoleByName('Content Manager');
    
    $options = array(
        // 'query' => 'content',
        // 'name' => 'content',
        'page' => 1,
        'limit' => 20,
    ); 
    
    $roles = $securityModel->getRoles($options);
    $numRoles = $securityModel->countRoles($options);
    
    // obtain all permissions
    $permissions = $securityModel->getPermissions();
}

function handleSecurity(SecurityManager $securityManager, Request $request) {
    // set the request to the security manager to detect the user from previous requests
    $securityManager->setRequest($request);
    
    // get the current user
    $user = $securityManager->getUser();
    if (!$user) {
        // no user logged in from a previous request
        try {
            $securityManager->login('admin', 'secure password');
        } catch (UsernameAuthenticationException $exception) {
            // invalid username
        } catch (PasswordAuthenticationException $exception) {
            // invalid password
        } catch (EmailAuthenticationException $exception) {
            // email is not confirmed
        } catch (InactiveAuthenticationException $exception) {
            // user is inactive
        }
    }
    
    // perform some checks
    if ($securityManager->isPermissionGranted('my.permission')) {
        // user is granted
    } else {
        // user is denied
    }
    
    if ($securityManager->isPathAllowed('/admin/system', 'GET')) {
        // user is allowed
    } else {
        // user is denied
    }
    
    if ($securityManager->isUrlAllowed('https://www.foo.bar/admin/system')) {
        // user is allowed
    } else {
        // user is denied
    }
    
    // mock an other user through the switch user feature
    try {
        $securityManager->switchUser('cm');

        // perform a check on the switched user
        if ($securityManager->isPermissionGranted('my.permission')) {
            // switched user is granted
        } else {
            // switched user is denied
        }
        
        // logout the switched user
        $securityManager->logout();
    } catch (UserNotFoundException $exception) {
        // requested user does not exist
    } catch (UserSwitchException $exception) {
        // can't switch to a super user as a non super user
    } catch (UnauthorizedException $exception) {
        // not allowed to switch user
    }
    
    // logout the current user
    $securityManager->logout();
}

实现

更多示例,您可以查看以下此库的实现:

安装

您可以使用Composer来安装此库。

composer require ride/lib-security