Apache Shiro Realms 中文说明文档

翻译 ALLEN ⋅ 于 2019-09-10 12:04:25 ⋅ 37 阅读

这是一篇协同翻译


一个 Realm 是一个能访问特定应用安全数据的组件,比如 users(用户表),roles (角色表)和 permissions(权限表)。这 Realm 把特定应用的数据转换成 Shiro 能理解的数据格式,所以 Shiro 转而提供一个单一的易于理解的 Subject 可编程 API,而不管数据源有多少个或你的数据是怎样的特定于应用程序。

各Realms 通常与数据源是 1 对 1 的关系,比如关系型数据库,LDAP 目录,文件系统,或者其他类似的资源。同样地,Realm 接口的实现使用特定数据源的 API 来发现授权数据(roles-角色, permissions-权限),比如 JDBC,File IO,Hibernate 或者 JPA,或者 任意其他数据访问 API。

一个 Realm 本质上是 security-specific(特定安全的) DAO

因为这些数据源的大部分通常不仅存储授权数据(比如roles-角色和permissions-权限)也存储验证数据(凭证,比如密码),每个Shiro Realm 都可以执行验证和授权操作。

Realm 配置

如果使用 Shiro’s INI 文件配置,那么你可以像其他任意对象一样在 [main] 片段 定义和引用 Realms ,但是它们是以显示或者隐式形式在 securityManager 上进行配置。

显示赋值

基于目前为止的 INI 配置的知识,这是一个显而易见的配置方式。定义一个或多个 Realms 之后,你可以在 securityManager 对象的集合属性上设置它们。

比如:

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

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

显示赋值是确定无疑的 - 你不仅可以控制它们用于认证和授权的顺序而且还可以准确控制那个 realms 被使用。Realm 顺序的影响在认证一章中的 认证序列 一节详细描述。


隐式赋值

如果你改变了 realms 定义的顺序,那么隐式赋值会导致意想不到的行为。推荐是避免使用这种方式而是使用显示赋值,其具有确定性的行为。隐式赋值很可能在 Shiro 将来的发布版本中被移除。
如果因为某些原因你不想用显示赋值配置 securityManager.realms 属性,那么你可以让 Shiro 检测所有可配置的 realms 并且把它们直接赋值给 securityManager。

使用隐式方法,realms会根据它们定义的顺序被赋值给 securityManager 实例。

也就是,对于下面的 shiro.ini 例子:

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

# no securityManager.realms assignment here

本质上,下面的代码行有着一样的效果:

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

但是,意识到使用隐式赋值,realms 定义的顺序会直接影响到验证和授权的方式。如果你改变了它们的定义顺序,则将更改主身份验证器 身份验证序列 函数。

由于这个原因,并且为了确保确定性行为,我们推荐使用显示赋值而不是隐式赋值。


Realm 身份验证

一旦你理解了 Shiro’s 主身份验证流程, 在身份验证尝试期间,准确地知道身份验证器与 Realm 交互时发生了什么是很重要的。

支持身份验证Tokens

正如在验证流程提到的,就在 Realm 执行身份验证尝试之前,它的 supports 方法会被调用。如果返回值是 true,那么之后只有 getAuthenticationInfo(token) 方法会调用。

通常,realm 会检查所提交的 token的类型(接口类型或者类类型)来判断是否可处理。 比如,处理生物数据的 Realm 可能根本不理解 UsernamePasswordTokens,这种情况,supports 方法会返回 false。

处理 支持的身份验证Tokens

如果 Realm 支持所提交的 AuthenticationToken,那么 Authenticator(身份验证器)会调用 Realm 的 getAuthenticationInfo(token) 方法。这实际上表示使用 Realm 的后端数据源进行身份验证尝试。该流程按顺序描述如下:

  • 为验证 principal (账号认证信息)检查 token
  • 基于 principal, 在数据源寻找相应的账号数据。
  • 确认提供的 token 凭证匹配存储中的 token 数据。
  • 如果凭证匹配,那么返回一个 Shiro 能理解的格式并且包含账号数据的 AuthenticationInfo 实例。
  • 如果凭证不匹配,那么会抛出 AuthenticationException 异常。

这是所有 Realm getAuthenticationInfo 实现的最高级别工作流。在这个方法里,Realms 可以做任意它们想做的事情,比如在审计日志中记录身份验证尝试,更新数据记录,或者对数据存储的身份验证尝试有意义的其他事情。

唯一需要的事是,如果凭证匹配给定的 principal(s),那么返回一个非空AuthenticationInfo 实例,该实例表示来自该数据源的 Subject 帐户信息。

直接实现 “Realm” 接口可能很费时,而且容易出错。大多数人选择将AuthorizingRealm 抽象类子类化(使用继承 AuthorizingRealm 抽象类),而不是从零开始。该类实现了通用的身份验证和授权工作流,以节省您的时间和精力。


凭证匹配

在上述 realm 验证工作流中,一个 Realm 必须验证 Subject 对象所提交的凭证(比如密码)是否与存储在数据存储中的凭证匹配。如果它们匹配,那么身份验证成功,并且此时系统已经验证了 end-user 的凭证。

对提交的凭证与 Realm 后端数据存储的凭证进行匹配是 Realm 的职责,而不是 ‘Authenticator’ 的职责。每个 'Realm' 都熟悉凭证格式和存储,可以执行详细的凭证匹配,而 'Authenticator' 是通用的工作流组件。

凭证匹配过程在所有应用几乎是相同的,并且通常只有数据比较而不同。为了确保这个过程是可插拔的,并在必要时可定制的,AuthenticatingRealm 对象以及它的子类支持 CredentialsMatcher 的概念来执行凭证比较。

发现账号数据后,AuthenticatingRealm和所提交的 AuthenticationToken 呈现给 CredentialsMatcher 来判断所提交的与数据存储内的凭证是否匹配。

Shiro 有可以让你开始使用的 CredentialsMatcher 实现, 比如 SimpleCredentialsMatcherHashedCredentialsMatcher 实现,但是如果你想为自定义匹配逻辑配置自定义实现,那么你应该直接这样做:

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
...

简单的相等性检查

Shiro 所有开箱即用(out-of-the-box)的 Realm 默认实现是使用 SimpleCredentialsMatcher。 SimpleCredentialsMatcher 对存储的帐户凭据和 AuthenticationToken 中提交的凭证执行简单的直接相等性检查。

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

SimpleCredentialsMatcher 不仅仅对字符串执行直接相等比较。它可以处理最常见的字节源,如字符串、字符数组、字节数组、文件和 InputStreams 。更多信息请参见 JavaDoc 说明文档。

Hash 化的凭证

存储最终用户凭证(例如密码)的一种更安全的方法是,在将凭证存储到数据存储之前先对其进行单向哈希,而不是以原始形式存储凭证并执行原始的 / 直接的比较。
这确保了最终用户的凭证永远不会以原始形式存储,也没有人能够知道原始(original)/原本(raw)值。这是一种比纯文本或原始格式比较更安全的机制,所有安全意识强的应用程序都应该支持这种方法,而不是非 hash 存储。

为了支持这些首选的加密哈希策略,Shiro 提供了在 realms 中可配置的HashedCredentialsMatcher 实现,而不是前面提到的 SimpleCredentialsMatcher。


Hashing credentials and the benefits of salting and multiple hash iterations are outside the scope of this Realm documentation, but definitely read the HashedCredentialsMatcher JavaDoc which covers these principles in detail.

Hashing and Corresponding Matchers
So how do you configure a Shiro-enabled application to do this easily?

Shiro provides multiple HashedCredentialsMatcher subclass implementations. You must configure the specific implementation on your realm to match the hashing algorithm you use to hash your users’ credentials.

For example, let’s say your application uses username/password pairs for authentication. And due to the benefits of hashing credentials described above, let’s say you want to one-way hash a user’s password using the SHA-256 algorithm when you create a user account. You would hash the user’s entered plain-text password and save that value:

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

//We'll use a Random Number Generator to generate salts.  This 
//is much more secure than using a username as a salt or not 
//having a salt at all.  Shiro makes this easy. 
//
//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);

Since you’re SHA-256 hashing your user’s passwords, you need to tell Shiro to use the appropriate HashedCredentialsMatcher to match your hashing preferences. In this example, we create a random salt and perform 1024 hash iterations for strong security (see the HashedCredentialsMatcher JavaDoc for why). Here is the Shiro INI configuration to make this work:

[main]
...
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# base64 encoding, not hex in this example:
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
# This next property is only needed in Shiro 1.0\.  Remove it in 1.1 and later:
credentialsMatcher.hashSalted = true

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

SaltedAuthenticationInfo
The last thing to do to ensure this works is that your Realm implementation must return a SaltedAuthenticationInfo instance instead of a normal AuthenticationInfo one. The SaltedAuthenticationInfo interface ensures that the salt that you used when you created the user account (e.g. the user.setPasswordSalt(salt); call above) can be referenced by the HashedCredentialsMatcher.

The HashedCredentialsMatcher needs the salt in order to perform the same hashing technique on the submitted AuthenticationToken to see if the token matches what you saved in the data store. So if you use salting for user passwords (and you should!!!), ensure your Realm implementation represents that by returning SaltedAuthenticationInfo instances.

Disabling Authentication

If for some reason, you don’t want a Realm to perform authentication for a data source (maybe because you only want the Realm to perform authorization), you can disable a Realm’s support for authentication entirely by always returning false from the Realm’s supports method. Then your realm will never be consulted during an authentication attempt.

Of course at least one configured Realm needs to be able to support AuthenticationTokens if you want to authenticate Subjects.


Realm Authorization

SecurityManager delegates the task of Permission or Role checking to Authorizer, defaulted to ModularRealmAuthorizer.

Role based Authorization
When one of the overloaded method hasRoles or checkRoles method is called on Subject

  • Subject delegates to SecurityManager for identifying if the given Role is assigned

  • SecurityManager then delegates to Authorizer

  • Authorizer then referrers to all the Authorizing Realms one by one until it found given role assigned to the subject. Deny access by returning false if no none of the Realm grants Subject given Role

  • Authorizing Realm AuthorizationInfo getRoles() method to get all Roles assigned to Subject

  • Grant access if it found the given Role in list of roles returned from AuthorizationInfo.getRoles call.
    Permission based Authorization
    When one of the overloaded method isPermitted() or checkPermission() method are called on Subject:

  • Subject delegates the task to grant or deny Permission to SecurityManager

  • SecurityManager then delegates to Authorizer

  • Authorizer then referrers to all of the Authorizer Realms one by one until it Permission is granted
    If Permission is not granted by any of the Authorizing Realm, Subject is denied Permission

  • Authorizing Realm does the following in order to check if a Subject is permitted:
    a. First it gets identify all Permissions assigned to Subject directly by calling getObjectPermissions() and getStringPermissions methods on AuthorizationInfo and aggregating the results.

b. If a RolePermissionResolver is registered, it is used to retrieve Permissions based on all of the roles assigned to Subject by calling the RolePermissionResolver.resolvePermissionsInRole()

c. For aggregated Permissions from a. and b. the implies() method is called to check if any of these permission are implied the checked permission. See WildcardPermission

点赞
回复数量: 0
    暂无评论~~
    • 请注意单词拼写,以及中英文排版,参考此页
    • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
    • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
    • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
    • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
      请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!
    Ctrl+Enter