Realm是一个可以访问应用特定的安全数据(如用户,角色和权限)的组件。Realm把应用的特定安全数据翻译成一种Shiro可以理解的格式,所以不管有多少安全数据源存在,也不管你的应用的安全数据有多特别,Shiro都可以提供一个简单易懂的 Subject API。

Realm和数据源通常是一对一的关系,数据源可能是关系型数据库,LDAP目录,文件系统,或者其他类似的资源。因此,Realm实现是用各种不同的数据源的API来获取授权数据(角色、权限等等)的,比如JDBC,文件IO,Hibernate或JPA,或者任何其他的数据API。

Realm实际上是一个安全数据源的DAO

因为这些数据源大部分都在存储授权数据(像角色或权限)的同时存储认证数据(像密码一样的凭证),每一个Realm都能同时执行认证和授权操作。

Realm 配置

如果用Shiro的INI配置,像其他对象一样,你可以在[main]部分定义和引用Realm,但是Realm配置在securityManager上面有两种方式:显式的和隐式的。

显式分配

基于到目前为止我们对INI配置的认识,这是一个很明显的配置方式。在定义了一个或多个Realm后,你把它们当做一个集合属性设置给securityManager对象。

例如:

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

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

显式分配是确定的——你可以精确地控制它们在认证和授权的时候的执行顺序,在认证章节的认证步骤部分( Authentication Sequence)对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

和后面加上这行配置效果是一样的:

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

然而,当使用隐式分配的时候必须要意识到,仅仅是Realm被定义的顺序,就会直接影响到当用户进行认证和授权尝试时,这些Realm被调用的情况。如果你改变了定义顺序,你就改变了主认证人的认证步骤( Authentication Sequence)功能。

基于这个理由,并且保证精确的行为,我们推荐用显式分配,不要用隐式分配。
Realm Authentication

你一旦理解了Shiro的主认证流程( Authentication workflow),那么了解在一次认证尝试期间Authenticator和Realm到底是怎么交互的就变得很重要了。
支持AuthenticationTokens
正如认证顺序( authentication sequence)一节提到的,在Realm被请求执行一个认证尝试之前,Realm的 supports方法会先被调用。如果这个方法返回true,才会继续调用Realm的getAuthenticationInfo(token) 方法。

通常Realm会检查提交的这个token的类型(接口或类),以判断能否处理它。例如,一个专门处理生物特征识别的Realm可能就完全不会理解 UsernamePasswordTokens  ,这时这个Realm的supports方法就会返回false。
处理支持的 AuthenticationTokens
如果Realm支持提交过来的 AuthenticationToken , Authenticator  将调用Realm的 getAuthenticationInfo(token) 方法。这有效地代表了通过Realm支持的数据源进行的一次认证尝试。这个方法按顺序执行如下步骤:
  1. 检查token的主体身份(账户身份信息)
  2. 基于主体信息,在数据源中查找相应的账户数据
  3. 确保token提供的凭证( credentials )能匹配上数据存储里的值
  4. 如果凭证匹配上了,就返回一个 AuthenticationInfo实例,这个实例封装了账户数据,并以一种Shiro可以理解的格式封装
  5. 如果凭证匹配不上,抛出一个 AuthenticationException异常

这是所有Realm的getAuthenticationInfo方法实现的最高级别工作流程。Realm在这个方法中可以自由地做任何事情,在审计日志中把这次认证尝试记录下来,更新数据记录,或对于这次认证尝试的数据存储任何有意义的操作。

唯一的约束就是,如果凭证匹配提供的主体,数据源必须返回一个非空 AuthenticationInfo  实例,这个实例包含了主体的账户信息。

节省时间

直接实现Realm接口可能会非常耗时并且容易出错。大部分人选择继承抽象类 AuthorizingRealm  ,而不是重头开始。这个类实现了通用的认证和授权工作流程,可以节省你的时间了精力。 

凭证匹配(Credentials Matching
在上面Realm的认证流程中,Realm必须校验主体提交过来的凭证(比如,密码),它必须和数据存储中的凭证相匹配。如果匹配上了,认证就认为是成功的,也就是说这时系统已经验明了终端用户的身份。

Realm凭证匹配

把被提交的凭证和Realm支持的数据存储里的凭证匹配起来,是每一个Realm的责任,而不是Authenticator的责任。每个Realm都最清楚自己的凭证格式和存储方式,只有知道这些才知道具体怎么匹配,而Authenticator是一个通用的工作流程组件。

凭证匹配的过程在所有应用中几乎一样,通常只有数据比对那个环节不同。为了保证该过程可插拔和可定制, AuthenticatingRealm 和它的子类支持作为一个具体的凭证匹配器( CredentialsMatcher),来执行凭证比对。

在找出账户数据后,账户数据和提交的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
...

Simple Equality Check
Shiro中所有现成的Realm实现都默认使用 SimpleCredentialsMatcher。SimpleCredentialsMatcher直接让存储的账户凭证和AuthenticationToken提交的凭证二者做个等式检查。

例如,如果提交了一个 UsernamePasswordToken ,SimpleCredentialsMatcher就会校验提交过来的密码是否和数据库中的密码相等。

SimpleCredentialsMatcher不仅可以用来比较字符串。它可以用来比较大多数通用的字节源,比如字符串,字符数组,字节数组,文件和输入流。想了解更多请查看它的JavaDoc。
Hashing Credentials
比起直接存储凭证的原始格式并进行直接比较,在把终端用户的凭证(比如密码)存储起来之前先进行单向哈希运算,会安全的多。

这保证了终端用户的凭证不会直接把原始格式存储起来,所以没有人知道原始值。这是一种比纯文本或原始比较要安全的多的机制,所有安全敏感的应用都应该支持对非哈希存储使用这种方法。

为了支持这些更加推荐的加密哈希策略,Shiro支持给Realm配置 HashedCredentialsMatcher实现,替换前面提到的SimpleCredentialsMatcher。

哈希化凭证,加盐的好处和多次迭代哈希,这些主题都超出了Realm文档所描述的范围。但是在 HashedCredentialsMatcher JavaDoc中对这些主题都有详细的描述。
Hashing and Corresponding Matchers
但是你该如何配置一个嵌入了Shiro的应用,使之能简单地使用哈希凭证?

Shiro提供了很多HashedCredentialsMatcher子类实现。你必须给你Realm配置具体的实现,用来匹配你给你的用户凭证使用的哈希算法。

例如,假使你的应用使用“用户名/密码 对”进行认证。由于上面描述了那么多关于哈希凭证的好处,比方说当创建一个用户的时候,你想用 SHA-256算法来单向哈希用户的密码。你要把用户提交过来的纯文本密码先哈希,然后保存那个哈希值。

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);

由于你用的是SHA-256算法,你要告诉Shiro使用合适的HashedCredentialsMatcher来匹配你的哈希选择。在这个例子中,我们创建了一个随机的盐,并且执行了1024次哈希迭代,已达到强安全性(想知道为什么请参看HashedCredentialsMatcher JavaDoc)。下面是Shiro INI配置使上面的代码能工作:

[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

最后要保证的是,你的Realm实现必须要返回一个 SaltedAuthenticationInfo实例,而不是一个普通的AuthenticationInfo实例。SaltedAuthenticationInfo实例可以让你获取当你创建这个账户时使用的盐(比如上面调用的user.setPasswordSalt(salt)),HashedCredentialsMatcher要用到这个盐。

HashedCredentialsMatcher需要这个盐,因为有了它才能对提交的AuthenticationToken执行相同的哈希技术,来判断这个token是否匹配数据存储中的哈希值。所以如果你对用户密码加密使用了盐(强烈建议你这么做),就必须保证你的Realm实现可以返回SaltedAuthenticationInfo实例。
Disabling Authentication
因为某种原因,你可能不想让某个Realm执行认证(你可能只想让它执行授权),你可以禁用该Realm对认证的支持,通过让Realm的supports方法返回一个false。这样的话,当认证尝试的时候,你的Realm就永远不会被访问了。

当然,要保证只要一个配置了的Realm能支持AuthenticationTokens,如果你想认证主体的话。
Realm Authorization
暂无





原文地址