webiny/security

Webiny 安全组件

v1.6.1 2017-09-29 08:12 UTC

README

安全组件是一个处理您身份验证和授权过程的层。

安装组件

安装组件的最佳方式是使用 Composer。

composer require webiny/security

要获取包的附加版本,请访问Packagist 页面

关于

在深入了解之前,您需要熟悉授权、身份验证和访问控制的术语,如果您不熟悉,请阅读以下文章

如果您想了解更多

使用方法

注意:访问防火墙有两种方式。

长方法

$firewall = $this->security()->firewall('admin');

短方法

$firewall = $this->security('admin');

组件的使用相当简单

首先处理用户登录

class MyClass
{
    use SecurityTrait;

    function loginUser()
    {
        $loginSuccessful = $this->security('admin')->processLogin();
    }
}

然后您可以尝试各种授权方法

class MyClass
{
    use SecurityTrait;

    function myMethod(){
        // get authenticated user
        $user = $this->security('admin')->getUser();

        // check if user has a role
        if($user->hasRole('ROLE_EDITOR')) {
            // user has role ROLE_EDITOR or any role with greater access level
        }

        // check if current user can access the current url
        if($this->security('admin')->isUserAllowedAccess()){
            // user can access the current url based on the defined access rules
        }
    }
}

如果您想注销用户

class MyClass
{
    use SecurityTrait;

    function logoutUser()
    {
        $logoutSuccessful = $this->security('admin')->processLogout();
    }
}

示例配置

这是一个示例配置。接下来的几个主题将描述配置的每个部分。

Security:
    UserProviders:
        Memory:
            john: {password: secret, roles: 'ROLE_USER'}
            admin: {password: login123, roles: 'ROLE_SUPERADMIN'}
    AuthenticationProviders:
        OAuth2:
            Params:
                Server: FBAuth
                Roles: [ROLE_USER]
    Firewalls:
        Admin:
            RealmName: Administration
            Anonymous: true
            RememberMe: true
            TokenKey: SecretKey
            UserProviders: [Memory, OAuth2]
            AuthenticationProviders: [Http, Form, OAuth2]

组件

安全层实际上是一组相互协作并通信的组件。接下来的几个部分将介绍这些组件并解释它们的作用。

令牌 (Security.Tokens)

令牌用于加密用户数据并将其保存到会话或cookie中。使用令牌,一旦用户获得授权,我们就可以通过令牌进行所有未来的授权,无需使用任何身份验证提供者、检查数据库等。令牌可以节省大量的处理时间。

每个令牌都有两个配置参数

  • Driver:已注册的 Crypt 服务的名称
  • Params:传递给 Driver 构造函数的参数列表
  • SecurityKey:将用于加密令牌数据的密钥;密钥长度必须为2n(8、16、32、64个字符)

示例令牌定义

Security:
    Tokens:
        SomeTokenName:
            Driver: '\Webiny\Component\Security\Token\CryptDrivers\Crypt\Crypt'
            SecurityKey: $3cR3tK3y # secret key that will be used to encrypt the token data
    Firewalls:
        Admin:
            Token: SomeTokenName

内置的令牌驱动程序使用 Crypt 组件,这在大多数情况下应该足够。您也可以定义多个具有不同驱动程序或安全密钥的令牌,但每个防火墙只能使用一个令牌。

默认情况下,您无需定义 Token,一个内部令牌会自动为您设置。对于默认令牌,您只需要在防火墙下定义 TokenKey

Security:
    Firewalls:
        Admin:
            TokenKey: SecretKey

编码器 (Security.Encoders)

编码器是负责两件事情的服务,从提供的字符串创建密码散列并验证提交的密码是否与给定的散列匹配。编码器做与令牌类似的事情,但令牌加密用户数据,这些数据可以被解密,而编码器创建散列,这是一个不可逆的过程,因此您不能获取原始字符串。

编码器组件自带默认的 Crypt 驱动程序,它使用内置的 Crypt 组件进行散列和验证密码。

驱动程序需要您定义 Crypt 服务。只需在 Params 下提供服务的名称,您的编码器就准备好了。

示例编码器配置

# encoder configuration
Security:
    Encoders:
        EncoderOne:
            Driver: \Webiny\Component\Security\Encoder\Drivers\Crypt
        EncoderTwo:
            Driver: \Webiny\Component\Security\Encoder\Drivers\Plain
    Firewalls:
        Admin:
            Encoder: EncoderOne

要创建自定义编码器驱动程序,您需要创建一个实现 \Webiny\Component\Security\Encoder\EncoderDriverInterface 的类。

该组件还包含一个Plain驱动程序,它不编码密码,而是保持其原始格式。您还可以在Firewall下设置Encoder: false。这将使用Plain驱动程序。

默认情况下,您不需要定义一个Encoder,内部编码器会自动为您定义。

用户提供者 (Security.UserProviders)

用户提供者类似于数据库,其中Security组件查询用户。每个提供者由两部分组成,即用户提供者和用户类本身。提供者部分负责根据提交的登录凭证加载用户,而用户对象则负责验证提交的凭证与提供者加载的对象是否匹配。

存在四个内置用户提供者

  • 内存
  • OAuth2
  • TwitterOAuth
  • 实体

内存提供者

Memory提供者允许您直接在配置文件中定义用户,其形式如下

Security:
    UserProviders:
        MyTestMemoryUsers:
            john: {Password: secret, Roles: ROLE_USER}
            admin: {Password: login, Roles: [ROLE_SUPERADMIN, ROLE_GOD]}
        MyOtherMemoryUsers:
            marco: {password: polo, roles: ROLE_ADMIN}
    Firewall:
        Admin:
            UserProviders: [MyTestMemoryUsers, MyOtherMemoryUsers]

如您所见,您可以定义多个内存提供者。您想使用哪个取决于您如何定义防火墙。您可以使用它们两个,但我们将在稍后讨论这一点。

注意:请确保在使用Memory或包含原始格式密码的其他提供者(即密码未加密的提供者)的防火墙上将Encoder设置为false。(注意:切勿在生产代码中这样做)。

OAuth2提供者

OAuth2提供者依赖于OAuth2组件,并且必须与后续主题中描述的认证提供者一起封装。基本上,此提供者提供了使用任何OAuth2服务器(如Facebook、Google、LinkedIn等)进行用户认证的选项。

要配置OAuth2用户提供者,只需设置内置驱动程序的路径

Security:
    UserProviders:
        SomeOAuth2Provider:
            Driver: '\Webiny\Component\Security\User\Providers\OAuth2\OAuth2Provider'
    Firewall:
        Admin:
            UserProviders: [SomeOAuth2Provider]

TwitterOAuth提供者

遗憾的是,Twitter不支持OAuth协议的版本2,只支持版本1,因此我们创建了一个特殊的TwitterOAuth提供者。其配置与OAuth2用户提供者非常相似。

Security:
    UserProviders:
        MyTwitterOAuthProvider:
            Driver: '\Webiny\Component\Security\User\Providers\TwitterOAuth\TwitterOAuth'
    Firewall:
        UserProviders: [MyTwitterOAuthProvider]

实体提供者

此提供者使用与您的数据库关联的Entity组件。

Security:
    UserProviders:
        MyFromDatabaseProvider:
            Driver: '\Webiny\Component\Security\User\Providers\Entity\Entity'
            Params:
                Entity: 'My\App\Entities\User'
                Username: username
                Password: password
                Role: ROLE_USER
    Firewall:
        UserProviders: [MyFromDatabaseProvider]

Entity参数指向实体类。Username定义包含用户名的集合中的字段名。Password与用户名相同,只是指向密码字段。Role指向包含用户角色的集合字段,或者如果该字段不存在,则用作角色名称。

自定义用户提供者

要实现自定义用户提供者,您需要创建一个实现\Webiny\Component\Security\User\UserProviderInterface的类。您还需要创建一个继承自\Webiny\Component\Security\User\AbstractUser的用户类。就是这样,所有其他细节都在接口和抽象类内部描述。

组合多个用户提供者

每个防火墙可以使用一个或多个用户提供者。您可以根据需要将它们组合起来。我们将在后续主题中讨论这个问题。

认证提供者 (Security.AuthenticationProviders)

认证提供者是用于用户认证的方法。

这是认证提供者的一个示例配置

Security:
    AuthenticationProviders:
        Http:
            Driver: '\Webiny\Component\Security\Authentication\Providers\Http\Http'
        Facebook:
            Driver: '\Webiny\Component\Security\Authentication\Providers\OAuth2\OAuth2'
            Params:
                Server: Facebook # which OAuth2 server to use (defined under OAuth2 component)
                Roles: [ROLE_USER] # which role to assign to user authenticated with this provider
        TwitterOAuth:
            Driver: \Webiny\Component\Security\Authentication\Providers\TwitterOAuth\TwitterOAuth'
            Params:
                Server: ['MyTwitterApp'] # which twitter app to use (must be registered by TwitterOAuth component)
                Roles: [ROLE_USER] # which role to assign to user authenticated with this provider
    Firewall:
        Admin:
            AuthenticationProviders: [Http, Facebook, TwitterOAuth]

配置必须有两个参数,即定义用于处理认证的类的Driver参数,以及可选的Params参数,它将不同参数传递给驱动程序构造函数。

一些其他认证提供者可能需要额外的参数。

也存在四个内置认证提供者

Http认证提供者

这是基本的Http认证。驱动程序:\Webiny\Component\Security\Authentication\Providers\Http\Http

表单认证提供者

当您有一个用于用户身份验证的HTML登录表单时,请使用此提供程序。驱动程序:\Webiny\Component\Security\Authentication\Providers\Form\Form

您的HTML表单必须包含以下字段

  • 用户名
  • 密码
  • rememberme(可选;默认:"")

OAuth2身份验证提供程序

此提供程序使用OAuth2协议和OAuth2组件。支持的OAuth2服务器由OAuth2组件定义。驱动程序:\Webiny\Component\Security\Authentication\Providers\OAuth2\OAuth2

此提供程序需要更多的配置,以下是一个示例

Facebook:
    Driver: '\Webiny\Component\Security\Authentication\Providers\OAuth2\OAuth2'
    Params:
        Server: Facebook # which OAuth2 server to use (must be defined under OAuth2 component configuration)
        Roles: [ROLE_USER] # which role to assign to user authenticated with this provider

注意params部分内的两个属性,Server属性指向定义的OAuth2配置,而Roles参数定义了将由此提供程序认证的用户分配哪些角色。

TwitterOAuth身份验证提供程序

此身份验证提供程序与OAuth2身份验证提供程序非常相似,只是这个是为与Twitter OAuth服务器协同工作而设计的。

以下是一个配置示例

TwitterOAuth:
    Driver: \Webiny\Component\Security\Authentication\Providers\TwitterOAuth\TwitterOAuth'
    Params:
        Server: ['MyTwitterApp'] # which twitter app to use (must be registered by TwitterOAuth component)
        Roles: [ROLE_USER] # which role to assign to user authenticated with this provider

防火墙(Security.Firewalls

防火墙是控制身份验证层的中心组件。

您可以拥有多个防火墙集。每个防火墙由以下参数组成

  • RealmName
    • 当前防火墙的用户可读名称
  • 匿名
    • 是否允许在防火墙后面进行匿名访问
  • RememberMe
    • 您是否希望在一定时间内记住用户的凭据,还是只记住当前会话
    • 如果您想在页面上使用“记住我”功能,则此必须设置为true
  • Encoder
    • 如果您的密码已被散列(它们应该被散列),请在此处放置您的Encoder名称
    • 编码器名称必须与Security.Encoders下的名称匹配
  • Token
    • 防火墙将使用哪个令牌来加密用户数据
  • UserProviders
    • 这些都是防火墙将用于请求用户账户的用户提供程序
    • 您可以定义一组用户提供程序,防火墙将依次询问它们
  • AuthenticationProviders
    • 与仅返回给定用户名的用户提供程序不同,身份验证提供程序执行用户凭据的检查
    • 例如,身份验证提供程序会要求用户通过Facebook登录,但用户提供程序,使用Facebook API,会检索账户
  • AccessControl
    • 一组需要进入该区域的URL和角色(您可以在下面的章节中找到更多信息)
  • RoleHierarchy
    • 定义当前防火墙的角色的层次结构(您可以在下面的章节中找到更多信息)

访问控制

访问控制是处理授权的中心部分。在访问控制中,您定义一组规则,其中每个规则由一个Path和一个列表的Roles组成,这些角色是访问该路径所需的。

AccessControl:
    Rules:
        - {Path: '/^\/[a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+\/[a-zA-Z0-9]{13}$/', Roles: ROLE_ANONYMOUS}
        - {Path: '/^\/about/', Roles: [ROLE_USER,ROLE_EDITOR]}
        - {Path: '/^\/statistics/', Roles: ROLE_ANONYMOUS}
    DecisionStrategy: affirmative

如果规则不匹配,则内置的ROLE_ANONYMOUS将被返回为访问该路径所需的角色。

Voters

访问控制还有一个内部机制称为Voters。这些就像一个陪审团,可以投票

  • ACCESS ALLOWED
  • ACCESS_DENIED
  • ACCESS_ABSTAINED

有两个内置的投票者,AuthenticationVoter根据用户是否已认证进行投票,还有一个RoleVoter根据用户是否具有访问当前区域的必要角色进行投票。

投票者背后的逻辑创建得可以扩展它并添加自己的投票者。例如,您可以创建一个投票者,根据用户的IP地址允许或拒绝访问,就像一个黑名单过滤器。

要创建自定义投票者,你需要创建一个实现 \Webiny\Component\Security\Authorization\Voters\VoterInterface 的类。之后,你需要创建一个服务并将其标记为 Security.Voter 标签。

MyComponent:
    Services:
        MyVoter:
            class: \MyCustomLib\MyCustomVoter
            tags: [Security.Voter]

决策策略

决策策略是定义系统如何根据投票者的投票来做出裁决的属性,即允许或拒绝访问。

有三种不同的策略可以应用

  • 一致 - 所有投票者必须投票 ACCESS_ALLOWED 才允许访问
  • 肯定 - 只需一个 ACCESS_ALLOWED 投票就足以允许访问
  • 共识 - 多数胜出(平局拒绝访问)

角色层次结构(Security.RoleHierarchy

此组件主要自解释,它定义了可用的角色及其层次结构。

以下是一个示例

RoleHierarchy:
    ROLE_USER: ROLE_EDITOR
    ROLE_ADMIN: ROLE_USER

ROLE_USER 将有权访问所有需要 ROLE_USERROLE_EDITORROLE_ANONYMOUS 的区域。ROLE_ADMIN 将有权访问所有需要 ROLE_ADMINROLE_USERROLE_EDITORROLE_ANONYMOUS 的区域。

事件

组件会触发几个事件,你可以订阅这些事件

  • wf.security.login_invalid 在用户提交无效登录凭证时触发
  • wf.security.login_valid 在用户提交有效登录凭证时触发
  • wf.security.role_invalid 在认证用户尝试进入需要比当前角色更高的角色的区域时触发
  • wf.security.logout 在防火墙调用 processLogout 时触发
  • wf.security.not_authenticated 在用户尝试访问他没有适当授权级别(角色)的区域时触发

所有这些事件都传递一个 \Webiny\Component\Security\SecurityEvent 的实例。

还有一些特定于用户提供者的事件:OAuth2 用户提供者事件

  • wf.security.user.oauth2 在用户通过 OAuth2 提供者认证时触发

TwitterOAuth 用户提供者事件

  • wf.security.user.twitter 在用户通过 Twitter OAuth 提供者认证时触发

这两个事件中的每一个都返回一个不同的类,对于 OAuth2 是 Webiny\Component\Security\User\Providers\OAuth2\OAuth2Event,对于 Twitter 是 Webiny\Component\Security\User\Providers\TwitterOAuth。这两个类都有两个方法,一个返回一个包含我们从 OAuth(2) 服务器获取的不同用户信息的对象。另一个方法返回 OAuth 类的实例,无论是 TwitterOAuth 还是 OAuth2,都提供对 API 和访问密钥的直接访问。

使用提供者简码

如果您只需要一个用户或认证提供者的实例,您可以使用简码。使用简码,您不需要定义 Driver 参数,在认证提供者的情况下,您不需要在 Security.AuthenticationProviders 下定义 auth 提供者。

例如,这个配置可以写成更简短的形式

Security:
    UserProviders:
        OAuth2:
            Driver: '\Webiny\Component\Security\User\Providers\OAuth2\OAuth2Provider'
        TwitterOAuth:
            Driver: '\Webiny\Component\Security\User\Providers\TwitterOAuth\TwitterOAuth'
        Memory:
            john: {password: secret, roles: 'ROLE_USER'}
            admin: {password: login123, roles: 'ROLE_SUPERADMIN'}
        Entity:
            Driver: '\Webiny\Component\Security\User\Providers\Entity\Entity'
            Params:
                Entity: 'My\App\Entities\User'
                Username: username
                Password: password
                Role: ROLE_USER
    AuthenticationProviders:
        Http:
            Driver: '\Webiny\Component\Security\Authentication\Providers\Http\Http'
        Form:
            Driver: '\Webiny\Component\Security\Authentication\Providers\Form\Form'
        OAuth2:
            Driver: '\Webiny\Component\Security\Authentication\Providers\OAuth2\OAuth2'
            Params:
                Server: Facebook # which OAuth2 server to use (defined under OAuth2 component)
                Roles: [ROLE_USER] # which role to assign to user authenticated with this provider
        TwitterOAuth:
            Driver: \Webiny\Component\Security\Authentication\Providers\TwitterOAuth\TwitterOAuth'
            Params:
                Server: MyTwitterApp # which twitter app to use (must be registered by TwitterOAuth component)
                Roles: [ROLE_USER] # which role to assign to user authenticated with this provider
    Firewalls:
        Admin:
            RealmName: Administration
            Anonymous: true
            RememberMe: true
            UserProviders: [OAuth2, TwitterOAuth, Memory, Entity]
            AuthenticationProviders: [Http, Form, OAuth2, TwitterOAuth]

简短版本

Security:
    UserProviders:
        Memory:
            john: {password: secret, roles: 'ROLE_USER'}
            admin: {password: login123, roles: 'ROLE_SUPERADMIN'}
        Entity:
            Params:
                Entity: 'My\App\Entities\User'
                Username: username
                Password: password
                Role: ROLE_USER
    AuthenticationProviders:
        OAuth2:
            Params:
                Server: Facebook # which OAuth2 server to use (defined under OAuth2 component)
                Roles: [ROLE_USER] # which role to assign to user authenticated with this provider
        TwitterOAuth:
            Params:
                Server: MyTwitterApp # which twitter app to use (must be registered by TwitterOAuth component)
                Roles: [ROLE_USER] # which role to assign to user authenticated with this provider
    Firewalls:
        Admin:
            RealmName: Administration
            Anonymous: true
            RememberMe: true
            UserProviders: [OAuth2, TwitterOAuth, Memory, Entity]
            AuthenticationProviders: [Http, Form, OAuth2, TwitterOAuth]

关键是 UserProviderAuthenticationProvider 的名称必须与内部驱动程序名称匹配。这对于内部提供者有效,对于自定义提供者,您始终需要定义 Driver

资源

要运行单元测试,您需要使用以下命令

$ cd path/to/Webiny/Component/Security/
$ composer.phar install
$ phpunit