Skip to content

Latest commit

 

History

History
269 lines (171 loc) · 17.3 KB

3.Realms.md

File metadata and controls

269 lines (171 loc) · 17.3 KB

Apache ShiroRealm


Realm是可以访问特定于应用程序的安全数据(如用户,角色和权限)的组件。Realm将这些数据转换成Shiro支持的形式, 所以无论有多少数据或者, 数据是什么样的格式, 我们都可以通过subject API进行操作。

Realm通常与数据源(例如关系数据库,LDAP目录,文件系统或其他类似资源)具有1对1的关联。因此,Realm的实现使用特定的数据源的API来发现授权数据(角色,权限等),例如JDBC,文件IO,Hibernate或JPA,或任何其他数据访问API。

Tips

Realm本质上是用于安全控制的DAO

由于大多数的这些数据库通常同时存储认证数据(凭证,例如密码),以及授权数据(例如角色或权限),所以Realm可以执行认证和授权操作。

如果使用Shiro的INI配置,配置Realm[main]区域中配置的其他对象一样, 它们可以通过以下两种方式之一进行配置在securityManager 中:显式或隐式。

这是一种显示的配置方法。定义一个或多个Realms后,将它们设置为securityManager对象上的集合属性。

例如:

fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm
bazRealm = com.company.baz.Realm

securityManager.realms = $fooRealm, $barRealm, $bazRealm

显式配置是确定性的 - 你可以精确的控制执行认证个授权操作的Realm的顺序。Realm认证顺序部分详细介绍了Realm排序效果。

不推荐

如果更改Realm的顺序,隐式配置可能会导致意外行为。建议您使用显示配置。可能会在未来的Shiro版本中弃用/删除这种配置方式。

如果由于某种原因您不想显式配置securityManager.realms属性,那么可以允许Shiro检测所有已配置的Realm并自动注入到securityManager

使用此方法,Realm 按照定义的顺序自动注入到securityManager

也就是说,对于以下shiro.ini示例:

blahRealm = com.company.blah.Realm
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm

# no securityManager.realms assignment here

基本上和以下配置相同:

blahRealm = com.company.blah.Realm
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm
securityManager.realms = $blahRealm, $fooRealm, $barRealm

但是,进行认证和授权操作时, realm的配置顺序会直接影响认证和授权的结果。如果更改了配置顺序,那么也要对Authenticator的认证顺序进行修改。

出于这个原因,我们建议使用显示配置而不是隐式配置。

了解了Shiro的认证流程,那么执行认证操作时, Authenticator 是如何与Realm进行交互的?

检查 AuthenticationTokens 类型

之前我们提到认证顺序Realm 执行认证操作前,会调用realm.supports(AuthenticationToken token) 方法。只有当返回值为truegetAuthenticationInfo(token) 方法才会被调用。

通常,Realm将检查提交的token(接口或类)是否是支持的类型。例如,处理生物识别数据的Realm可能根本不理解UsernamePasswordTokens,在这种情况下supports会但会false

处理支持 AuthenticationTokens

如果Realm 支持提交的AuthenticationTokenAuthenticator则会调用Realm的getAuthenticationInfo(token)方法。这代表Realm执行认证操作时查询后端数据。该方法依次为:

  1. 检查token识别身份(帐户识别信息)
  2. 基于此principal,在数据源中查找相应的帐户数据
  3. 确保提供的credentials与存储在数据存储中的相匹配
  4. 如果匹配,则返回AuthenticationInfo实例,该实例以Shiro理解的格式封装帐户数据
  5. 如果凭据不匹配,则抛出AuthenticationException

这是所有Realm的 getAuthenticationInfo 实现的最高级别工作流程。Realm可以在此方法中执行任何操作,例如在日志中记录认证请求,更新数据记录等。

唯一需要的是,如果凭证与给定的身份匹配,会返回一个非空的AuthenticationInfo 对象, 这个对象就是数据空中的主体的信息。

实现AuthorizingRealm 而不是Realm

直接实现Realm接口可能非常耗时且容易出错。大多数人选择继承AuthorizingRealm抽象类。此类实现了通用的身份验证和授权工作流,以节省您的时间和精力。

在上述的Realm认证流程中,Realm必须验证subject提交的凭证(例如密码)与存储在数据存储中的凭证是否匹配。如果匹配,则认为认证成功,并且系统拥有了可识别的身份。

Realm负责凭证的匹配

验证提交的凭证是否与数据库中存储的凭证是否向匹配由Realm负责, 而不是Authenticator。Realm知道凭证格式和存储方式,并且可以执行详细的凭证匹配,而Authenticator是一个通用的工作流组件。

凭证匹配过程在所有应用程序中几乎相同,并且通常仅根据比较的数据而不同。为确保此过程可插拔并在必要时可自定义,AuthenticatingRealm及其子类支持CredentialsMatcher的概念支持自定义凭证匹配。

在获取帐户数据之后,将账户数据(就是AuthenticationInfo)和提交AuthenticationToken呈现给 CredentialsMatcher , CredentialsMatcher 会调用equals 方法对两者进行比较。

Shiro有一些CredentialsMatcher 实现可以直接使用,例如SimpleCredentialsMatcherHashedCredentialsMatcher 对象,但如果您想为Realm配置自定义的CredentialsMatcher,您可以直接执行此操作:

Realm myRealm = new com.company.shiro.realm.MyRealm();
CredentialsMatcher customMatcher = new com.company.shiro.realm.CustomCredentialsMatcher();
myRealm.setCredentialsMatcher(customMatcher);

或者,如果使用Shiro的INI 配置

[main]
...
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
myRealm = com.company.shiro.realm.MyRealm
myRealm.credentialsMatcher = $customMatcher
...

简单的相等检查 Simple Equality Check

Shiro的所有可以直接使用的Realm 实例都默认使用SimpleCredentialsMatcherSimpleCredentialsMatcher 调用doCredentialsMatch 方式, 该方法会调用equals方法, 将凭证对象转换正byte数组进行比较。如图

doCredentialsMatch方法

equals方法

例如,如果提交了UsernamePasswordToken,则SimpleCredentialsMatcher验证提交的密码是否与存储在数据库中的密码完全相同。

SimpleCredentialsMatcher 执行凭证匹配时, 尽管大多数情况下是直接匹配字符串。但是它也可以与大多数常见字节一起使用,例如字符串,字符数组,字节数组,文件和InputStream。有关更多信息,请参阅其JavaDoc。

哈希凭证 Hashing Credentials

为了安全起见,一般不会将用户凭证(例如: 密码)直接存储, 而不是将其转换hash散列然后在存储在数据库中。

译者: 这也就是加密, 例如一个用户的登录密码是123, 在数据库中如果直接存储123 如果数据库被盗, 会产生很严重的后果. 所以我们可以通过算法对密码123 加密. 会得到一串字符串, 在将得到的字符串存储到数据库中(加密算法一般都是单向的, 无法通过密匙转换成明文, 即使数据库被盗也无法获取密码), 在进行认证时, 先将用户提供的凭证进行加密, 然后跟数据库中存储的加密后的密钥进行匹配, 如果匹配成功, 则认证通过

这可以确保最终用户的凭据永远不会以原始形式存储,并且没有人能够知道原始/原始值。这是一种更安全的机制,所有注重安全性的应用程序都应该优先考虑加密存储。

为了支持这些首选的加密散列策略,Shiro提供了在Realm上配置的HashedCredentialsMatcher实现,而不是前面提到的SimpleCredentialsMatcher

散列凭证以及salting和多个散列迭代的好处超出了本文档的范围,但可以阅读HashedCredentialsMatcher JavaDoc,它详细介绍了这些原则。

哈希和相应的匹配者 Hashing and Corresponding Matchers

那么如何配置来完成此凭证匹配操作?

Shiro提供了多个HashedCredentialsMatcher子类实现。您必须在Realm上配置特定实现,以匹配对用户凭证加密的hash算法。

例如,假设您的应用程序使用用户名/密码对进行身份认证。并且由于上面描述的散列凭证的好处,假设您希望在创建用户帐户时使用SHA-256算法对用户密码进行加密。您可通过如下方式对用户输入的纯文本密码进行加密并保存该值:

import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
...

//安全起见,我们通过一个随机数生成盐值, 这比直接使用用户名,或者不加盐更安全
//Note that a normal app would reference an attribute rather 
//than create a new RNG every time: 
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();

//Now hash the plain-text password with the random salt and multiple 
//iterations and then Base64-encode the value (requires less space than Hex): 
String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt, 1024).toBase64();

User user = new User(username, hashedPasswordBase64);
//save the salt with the new account.  The HashedCredentialsMatcher 
//will need it later when handling login attempts: 
user.setPasswordSalt(salt);
userDAO.create(user);

由于使用了SHA-256算法对密码进行哈希处理,因此您需要告诉Shiro使用对应的HashedCredentialsMatcher来匹配您的哈希首选项。在这个例子中,我们创建一个随机salt并执行1024次哈希迭代以获得强大的安全性(请参阅HashedCredentialsMatcherJavaDoc了解原因)。以下是使用这项工作的Shiro INI配置:

[main]
...
# 这里需要使用Sha256CredentialsMatcher作为凭证匹配器,因为数据库中存储的是Sha256加密后的数据
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# 不适用Hex编码,设置为false会使用Base64进行编码:
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
# 1.1以上版本不需要下面这行:
credentialsMatcher.hashSalted = true

...
myRealm = com.company.....
myRealm.credentialsMatcher = $credentialsMatcher
...

最后需要注意的是, 定义的Realm必须返回SaltedAuthenticationInfo 对象实例, 而不是普通的AuthenticationInfoSaltedAuthenticationInfo接口可确保您在创建的用户帐户中使用的盐(例如,user.setPasswordSalt(salt);)可以被HashedCredentialsMatcher 使用。

禁用认证 Disabling Authentication

如果由于某种原因,您不希望Realm执行身份认证(例如:只用Realm执行授权),你可以通过设置realm的supports方法使用返回false 来禁用身份认证。

当然,如果要对Subjects进行身份验证,至少配置一个Realm能够支持AuthenticationTokens。

Realm授权 Realm Authorization

SecurityManager委托Authorizer 执行角色和权限检查,默认使用ModularRealmAuthorizer

基于角色的授权 Role based Authorization

hasRolescheckRoles方法被调用时

  1. Subject委托SecurityManager 检查是否拥有给定的角色
  2. SecurityManager 然后委托给 Authorizer
  3. Authorizer 然后逐个引用所有授权Realm,直到它找到分配给主体的给定角色。如果没有任何Realm授予主题给定角色,则通过返回false拒绝访问
  4. 授权Realm AuthorizationInfo getRoles()方法获取分配给Subject的所有角色
  5. 如果在AuthorizationInfo.getRoles调用返回的角色列表中找到给定角色,则授予访问权限。
基于权限的授权 Permission based Authorization

当在Subject调用isPermitted()checkPermission()方法时:

  1. Subject 委托SecurityManager的进行权限校验1543396670800

  2. SecurityManager 委托给 Authorizer

  3. 然后 Authorizer 遍历所有Realm,直到授予权限. 如果所有Realm都为未授予权限,则拒绝主体权限

  4. Authorizing Realm 执行以下操作以检查是否拥有权限:

    a. 首先,它通过在AuthorizationInfo上调用getObjectPermissions()getStringPermissions方法并聚合结果来直接识别分配给Subject的所有权限。

    b. 如果注册了RolePermissionResolver,则会使用它来调用基于分配给Subject的所有角色的权限。RolePermissionResolver.resolvePermissionsInRole()

    C. 对于a. 和 b. 执行通过的Permissions会执行implies()方法 。请参阅WildcardPermission