Shiro中的Realm详解

一个Realm可以理解为能够访问特定于应用程序的安全数据(如用户,角色和权限)的组件。不管应用程序存在多少数据源以及任何类型的数据,Shiro都能通过Realm将其转换成易于理解的的程序化API格式。

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

因为通常情况下,大部分数据源既有认证数据(类似于密码)又有授权数据(类似于角色和权限),所以Shiro的Realm可以对认证和授权同样支持。

Realm配置

如果使用Shiro的INI配置,您可以将Realms像[main]部分中的任何其他对象一样去定义和引用,它们可以通过securityManager使用以下两种方式之一进行配置:显式或隐式。

明确的分配

基于之前对INI配置的了解,这是一种申明式的配置方法。定义一个或多个Realms后,将它们设置为securityManager对象的集合属性。

例如:

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

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

显式赋值是有确定性的 - 您可以准确地控制使用哪些Realm以及用于身份验证和授权的顺序。

隐含分配

如果基于某种原因您不想显式配置securityManager.realms属性,则可以允许Shiro检测所有已配置的Realm并将其直接分配给securityManager

使用此方法,域将按照定义的顺序分配给securityManager实例。

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

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

它基本上和下面的展示有一样的效果

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

如果你改变了Realms的定义顺序,隐式配置可能会造成一些未预期的影响。所以正常情况下建议用户使用显示配置。

Realm认证

如果您需要掌握Shiro的认证流程,那么了解在认证过程中AuthenticatorRealm是如何交互则显得格外重要。

支持AuthenticationTokens

在认证流程中我们已经提及到,在认证步骤之前我们就会调用到Realmsupports方法,只有当它返回truegetAuthenticationInfo(token)方法才会被执行。

通常Realm会检查提交上来的认证token是否是其支持的类型或class,例如,处理生物识别数据的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方法要返回来自该数据源的主体信息的一个非空实例。

凭证匹配

在上述Realm身份验证工作流程中,Realm必须验证Subject提交的凭据(例如密码)与存储在数据存储中的凭据匹配。如果匹配,则认为认证成功,并且系统已验证最终用户的身份。

凭证匹配过程在所有应用程序中几乎相同,并且通常仅仅不同于需要比较的数据。为确保此过程可插拔并在必要时可自定义,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);

或者在INI配置中添加以下内容:

[main]
...
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
myRealm = com.company.shiro.realm.MyRealm
myRealm.credentialsMatcher = $customMatcher
...
简单的相等性检查

Shiro所有开箱即用的Realm都是基于SimpleCredentialsMatcher实现的。SimpleCredentialsMatcher会简单的基于提交上来的AuthenticationToken的字面意义与存储的认证信息直接匹配。

例如,如果一个UsernamePasswordToken被提交,SimpleCredentialsMatcher会验证其密码是否和数据库中的密码是否一样。

SimpleCredentialsMatcher同时支持对字节数组,字符串,字符,文件以及输入流等类型的数据来比较判断相等性。

哈希证书

相比于直接将用户的密码存储到数据库中,一个更安全的方式就是存储用户密码的唯一哈希密文。

确定用户密码不会被直接存到数据库中且没有人知道其原始值与直接将密码以文本格式存储相比更安全,所有考虑到安全相关的应用都应该将其优先于非散列存储。

为了支持这些首选的加密散列策略,Shiro提供了HashedCredentialsMatcher来替代SimpleCredentialsMatcher,其支持加盐和多重散列迭代等策略。

哈希和相应的匹配者

那么如何轻松完成配置一个支持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;
...

//我们会使用一个随机数算法来生成盐 。
//这比直接使用用户名作盐更安全。
//通常app会直接使用RNG的引用,而不是自己定义。
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();

//现在用户密码已经加盐且实现了多重迭代的散列,最后使用Base64算法生成字符串
String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt, 1024).toBase64();

User user = new User(username, hashedPasswordBase64);
//将获取到的用户的新凭证存储到数据库中,以备登录使用。
user.setPasswordSalt(salt);
userDAO.create(user);

因为您使用了SHA-256算法来散列您的密码,所以您需要告诉Shiro应该使用HashedCredentialsMatcher方法来匹配您的认证过程。
例如:

[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引用。

禁用身份验证

如果由于某种原因,您不希望Realm对数据源执行身份验证(可能因为您只希望Realm执行授权),您可以通过始终从Realmsupports方法返回false来完全禁用Realm对身份验证的支持。然后身份验证期间将永远不会咨询您的Realm

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

Realm授权

SecurityManager将检查Permission或Role的任务委托给Authorizer的,其默认实现为ModularRealmAuthorizer。

基于角色的授权

  1. Subject委托SecurityManager来确认是否被分配了指定的角色信息
  2. 然后SecurityManager委托给Authorizer
  3. Authorizer逐个引用所有的授权Realm,直到它找到分配给Subject的给定角色。如果没有任何Realm授予Subject给定角色,则拒绝访问并返回false
  4. 授权的Realm的AuthorizationInfo.getRoles()方法会获取分配给Subject的所有角色
  5. 如果在AuthorizationInfo.getRoles方法返回的角色列表中找到给定角色,则授予访问权限。

基于权限的授权

当在Subject上调用isPermitted()或checkPermission()其中一个重载方法时:

  1. Subject 委派SecurityManager判断授予或拒绝任务的权限
  2. 然后 SecurityManager 委托给Authorizer
  3. 然后,Authorizer逐个引用所有授权Realm,直到权限被授予,如果任何授权Realm均未授予权限,则拒绝
  4. 授权Realm执行以下操作以检查Subject是否被允许:
    a. 首先,它通过在AuthorizationInfo上调用getObjectPermissions()getStringPermissions方法并聚合结果来直接识别分配给Subject的所有权限。
    b. 如果注册了RolePermissionResolver,则会调用RolePermissionResolver.resolvePermissionsInRole()方法来获取分配给Subject的所有角色的权限。
    c. 对于来自a和b的聚合权限。调用implies()方法来检查当前Subject是否包含所需权限。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值