Apache Shiro学习 - 5 - Realms
Realm 是Shiro中必须的安全数据源 ( A Realm is essentially a security-specific DAO )
Realm
是应用程序中获取诸如用户、角色、权限等安全数据的组件。Realm
将特定应用程序的数据转换成Shiro能够处理的格式,从而使Shiro能够提供一个单一的、易于理解的 Subject 编程API ,不论你数据源有多少、数据与特定的应用程序联系多么紧密(how application-specific your data might be)。Realm
通常与数据源,如关系数据库、LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)目录、文件系统或其他类似资源,具有1对1的关系,因而, Realm
接口的实现,需要使用特定的数据源API来发现权限数据(roles, permissions
等),包括 JDBC, File IO, Hibernate (MyBatis)或 JPA, 或其他 Data Access API.
由于多数数据源同时保存身份验证数据(如密码等凭证(credentials))和授权数据(如roles 或 permission),每个 Shiro 的Realm
都能够同时执行认证与授权操作( authentication and authorization operations).
Realm 配置
如果使用 Shiro的 INI 配置, 你可以像定义其他对象一样在[main]
部分定义 Realms
引用,但它们有两种方式配置到一个 securityManager
上去: 明确和不明确(explicitly or implicitly).
明确指定 | Explicit Assignment
基于INI配置知识我们可以知道,有一个明显的配置方法,就是定义一个或多个Realm
之后,将它们作为一个集合(collection)属性配置到securityManager
对象上去:
|
明确指定是确定性的(deterministic) - 你可以精确地控制你要使用的realm
及它们在验证与授权时的使用顺序。Realm
顺序的影响在将在 Authentication Sequence 部分详细说明.
模糊指定 | Implicit Assignment
不推荐 Not Preferred
不明确的指定可能在你改变realm
定义顺序时导致不可预测的问题。不建议使用此方法。Implicit Assignment 很有可能在将来的 Shiro 版本中移除。
如果某些原因使你不想明确地配置securityManager.realms
属性, 你可以让Shiro 去探测所有配置的realm
并且将它们直接指定给 securityManager
.
此方法中, realm
将根据它们定义时的顺序被指定给 securityManager
.
shiro.ini
例子:
|
基本和加上下面这条配置的效果一样:
|
但需要注意的是,只有定义realm
时的顺序会直接影响它们在验证和授权时如何被使用。若你修改了realm
定义的顺序, 你将会修改主验证器 Authenticator
的验证顺序( Authentication Sequence )功能。
因此,不建议使用 Implicit Assignment.
Realm身份验证 | Realm Authentication
一旦你理解了Shiro的身份验证工作流( Authentication workflow ),在尝试进行身份验证时清楚地知道验证器 Authenticator
如何与realm
交互是非常重要的。
验证令牌支持 | Supporting AuthenticationTokens
正如在 身份验证顺序 中所言, 在一个 Realm
被用来进行身份验证之前,它的 supports
方法被调用了. 只有当返回值为 true
, 它的 getAuthenticationInfo(token)
方法才会被激活.
Realm
通常会检查提交的令牌(token
)是否是它可以处理的类型(接口或类)。例如,一个 处理生物特征资料( biometric data)的 Realm是不能处理 UsernamePasswordTokens
的, 因而它的supports
方法将会返回 false
.
处理支持的认证令牌 | Handling supported AuthenticationTokens
若一个 Realm
支持( supports
)一个提交的令牌( AuthenticationToken
),验证器( Authenticator
)将会调用Realm的 getAuthenticationInfo(token) 方法. 这实际上表示了Realm
后面数据源的验证尝试(This effectively represents an authentication attempt with the Realm's
backing data source)。此方法的顺序是:
- 检查
token
中当事人(principal )的识别信息( identifying principal (账号识别信息)) - 基于
principal
, 在数据源中查找对应的账号数据 - 确保令牌提供分凭证
credentials
与保存在数据中的一致 - 若凭证相匹配, 将返回一个以Shiro能够处理的格式封装了账号信息的 AuthenticationInfo 实例
- 如果凭证不匹配, 抛出 AuthenticationException 异常
这是所有Realm的 getAuthenticationInfo
实现的最高级别工作流。 在此方法中,Realm可以自由地做任何处理, 例如记录登录尝试, 更新数据记录, 或其他任何与此数据源的认证尝试相关的操作.
唯一需要的是,若凭证与给定的 principal(s)匹配, 必须返回一个代表那个数据源的Subject的账号信息的非null的 AuthenticationInfo
实例。
捷径
直接实现
Realm
接口将会是耗时且容易出错的工作. 大部分人都选择实现其抽象子类 AuthorizingRealm 。此类实现了基本的验证和授权工作流,节约了你的时间和精力。
凭证比对 | Credentials Matching
在上述 realm
的认证工作流中, 一个Realm
必须要去核实 Subject 提交的凭证 (如密码) 必须与数据存储中的相匹配. 若它们相匹配,则验证被认为是成功的,系统核实了终端用户的身份。
Realm Credentials Matching
比对提交的凭证与
Realm
后面数据源中存储的凭证是每个Realm
的责任而非验证器的Authenticator
。 每个Realm
都精通凭证格式 和存储及详细的凭证匹配的知识, 而Authenticator
只是一个工作流组件.
凭证匹配过程在所有应用程序中几乎都是一样的,唯一的区别只是比较的数据。 为确保这一过程在需要时是可插拔和可自定义的, AuthenticatingRealm 和它的子类支持凭证匹配器( CredentialsMatcher )的概念来执行凭证比对。
当获取到账号数据之后, 此数据及提交的 AuthenticationToken
将被呈现给 CredentialsMatcher
以检查提交的数据是否与存储的数据相匹配.
Shiro已有了一些 CredentialsMatcher
的实现类以便于你快速开始开发,如 SimpleCredentialsMatcher 和 HashedCredentialsMatcher, 但若你想配置一个自己的实现类来实现自己的匹配逻辑,你可以直接这样做:
|
或者,使用 Shiro的 INI配置( configuration ):
|
简单相等比较 | Simple Equality Check
所有 Shiro 预设的(out-of-the-box) Realm
实现默认使用 SimpleCredentialsMatcher. SimpleCredentialsMatcher
对存储的账号数据与提交于 AuthenticationToken
中的数据执行一种简单直接的相等比较.
例如, 若提交了一个 UsernamePasswordToken , SimpleCredentialsMatcher
会将提交的密码与存储在数据库中的密码进行精确的相等比较 .
SimpleCredentialsMatcher
执行的相等比对并不仅仅是字符串比较. 它可以执行大部分常见的字节数据源的比对,如字符串(Strings), 字符数组( character arrays), 字节数组( byte arrays),文件( Files)和输入流( InputStreams). See its JavaDoc for more.
哈希凭证 | Hashing Credentials
与以原始格式存储和比对凭证相比,一种更安全的存储终端用户凭证(如密码)的方法是在将它们存储到数据库之前对其执行单向哈希( one-way hash)。
这确保了终端用户的密码永远不会以其原始形式存储,而且无人知道其原始值。这是比文本或原始数据比对更加安全的机制,任何有安全考虑的应用程序都应该采用这种方式。
为支持这种首选的密码散列策略(preferred cryptographic hashing strategies), Shiro 提供了 HashedCredentialsMatcher 实现类的配置来代替前述的 SimpleCredentialsMatcher
.
散列密码(Hashing credentials)和加盐 ( salting) 及多次哈希/散列(multiple hash iterations )的优点的讨论超出了 Realm
文档的范畴, 获取更多详细信息请参考 HashedCredentialsMatcher JavaDoc 。
哈希及相应的匹配器 | Hashing and Corresponding Matcher
那么,如何配置Shiro应用程序来简便的实现这种效果?
Shiro 提供了多个 HashedCredentialsMatcher
的子类实现. 你必须在你的realm
中配置特定的实现类来与你用来哈希用户密码的算法相匹配。
例如, 假设你使用用户名/密码对来进行验证 。由于上述优点,你想在插件用户账户时使用 SHA-256 算法来散列用户密码。 你应该散列用户输入的文本密码,并保存其值:
|
因为你使用了 SHA-256
来散列你的密码, 你需要告诉 Shiro 使用合适的 HashedCredentialsMatcher
来与你的选择相匹配 . 本例中, 我们创建了一个随机盐( salt )且执行了1024次迭代来增强安全 (查阅 HashedCredentialsMatcher
的 JavaDoc 了解为啥). 下面是 Shiro的相关 INI 配置:
|
SaltedAuthenticationInfo
确保上述设置工作的最后一件事是,你的 Realm
实现类必须返回一个 SaltedAuthenticationInfo 实例(通常是SimpleAuthenticationInfo
对象)而不是一个通常的AuthenticationInfo
. SaltedAuthenticationInfo
接口确保你在创建账户时使用的盐 (如上面的user.setPasswordSalt(salt);
) 可以被 HashedCredentialsMatcher
使用.
HashedCredentialsMatcher
需要盐以便对提交的 AuthenticationToken
执行相同的哈希操作,来查看令牌是否与你储存的数据相匹配. 因而,若你在用户密码中加盐(你应该这样做!),请确保你的 Realm
实现类返回了一个 SaltedAuthenticationInfo
实例.
禁用认证 | Disabling Authentication
若因为某些原因,你不想你的 Realm 对一个数据源进行认证(可能你只想利用Realm进行授权操作), 你应该使Realm的 supports
方法返回false
来完全禁用Realm对认证的支持. 这样你的Realm永远将不会在认证尝试时被使用.
当然,若你想对Subjects进行认证,请确保最少配置了一个 Realm
来支持 AuthenticationTokens .
Realm 授权 | Realm Authorization
待续 TBD