【Shiro】 领域

Metadata

title: 【Shiro】 领域
date: 2023-01-20 10:29
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/Shiro/基础
categories:
  - Shiro
keywords:
  - Shiro
description: 【Shiro】 领域 Realm

【Shiro】 领域

ARealm是一个组件,可以访问特定于应用程序的安全数据,例如用户、角色和权限。将此特定于应用程序的Realm数据转换为 Shiro 可以理解的格式,因此 Shiro 可以反过来提供一个易于理解的主题编程 API,无论存在多少数据源或您的数据可能是多么特定于应用程序。

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

因为大多数这些数据源通常同时存储身份验证数据(凭证,如密码)和授权数据(如角色或权限),所以每个 Shiro都Realm可以执行身份验证和授权操作。

领域配置

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

显式赋值

根据目前对 INI 配置的了解,这是一种显而易见的配置方法。定义一个或多个领域后,您可以将它们设置为对象的集合属性securityManager。

例如:

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

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

显式分配是确定性的——您可以准确控制使用哪些领域以及它们用于身份验证和授权的顺序。领域排序效果在身份验证章节的身份验证序列部分中有详细描述。

隐式赋值

如果由于某种原因你不想显式配置该securityManager.realms属性,你可以允许 Shiro 检测所有配置的领域并将它们securityManager直接分配给。

使用这种方法,领域按照它们被定义的顺序分配给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

但是,请注意,使用隐式分配时,定义域的顺序直接影响在身份验证和授权尝试期间如何查询它们。如果你改变它们的定义顺序,你将改变主要的“Authenticator’s Authentication Sequence的功能。

出于这个原因,并为了确保确定性行为,我们建议使用显式赋值而不是隐式赋值。

领域认证

一旦您了解了 Shiro 的主要身份验证工作流程,重要的是要准确了解在身份验证尝试期间Authenticator与 a 交互时会发生什么。Realm

配套AuthenticationTokens

如身份验证序列中所述,就在查询 aRealm以执行身份验证尝试之前,将supports调用其方法。如果返回值为true,那么它的getAuthenticationInfo(token)方法才会被调用。

通常,领域将检查提交的令牌的类型(接口或类)以查看它是否可以处理它。例如,处理生物识别数据的 Realm 可能根本不理解UsernamePasswordTokens,在这种情况下它会false从supports方法中返回。

处理支持AuthenticationTokens

如果Realm supports提交AuthenticationToken,Authenticator将调用 Realm 的getAuthenticationInfo(token)方法。Realm’s这有效地表示了对支持数据源的身份验证尝试。该方法,按顺序:

  1. 检查token识别主体(帐户识别信息)
  2. 根据 ,principal在数据源中查找对应的账号数据
  3. 确保提供的令牌credentials与存储在数据存储中的令牌相匹配
  4. 如果凭据匹配,则返回一个AuthenticationInfo实例,以 Shiro 理解的格式封装帐户数据
  5. 如果凭据不匹配,则抛出AuthenticationException

这是所有 RealmgetAuthenticationInfo实现的最高级别工作流程。在此方法期间,领域可以自由地做任何他们想做的事情,例如在审计日志中记录尝试、更新数据记录或任何其他对该数据存储的身份验证尝试有意义的事情。

唯一需要的是,如果凭据与给定主体匹配,AuthenticationInfo则返回一个非空实例,表示来自该数据源的主题帐户信息。

凭证匹配

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

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

发现帐户数据后,将其和提交AuthenticationToken的内容提交给 aCredentialsMatcher以查看提交的内容是否与存储在数据存储中的内容相匹配。

Shiro 有一些CredentialsMatcher实现可以让你开箱即用,例如SimpleCredentialsMatcher和HashedCredentialsMatcher实现,但如果你想为自定义匹配逻辑配置自定义实现,你可以直接这样做:

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 的所有开箱即用的Realm实现都默认使用SimpleCredentialsMatcher。对存储的SimpleCredentialsMatcher帐户凭据与在AuthenticationToken.

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

不过,SimpleCredentialsMatcher它不仅对字符串执行直接相等比较。它可以与最常见的字节源一起工作,例如字符串、字符数组、字节数组、文件和输入流。有关更多信息,请参阅其 JavaDoc。

哈希凭证

与以原始形式存储凭据并执行原始/普通比较不同,一种更安全的存储最终用户凭据(例如密码)的方法是先对它们进行单向散列,然后再将它们存储到数据存储中。

这确保了最终用户的凭据永远不会以原始形式存储,并且没有人可以知道原始/原始值。这是一种比纯文本或原始比较更安全的机制,所有注重安全性的应用程序都应该支持这种方法而不是非散列存储。

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

哈希凭证以及加盐和多次哈希迭代的好处超出了本Realm文档的范围,但请务必阅读HashedCredentialsMatcher JavaDoc,其中详细介绍了这些原则。

散列和相应的匹配器

那么如何配置一个支持 Shiro 的应用程序来轻松地做到这一点呢?

Shiro 提供了多个HashedCredentialsMatcher子类实现。您必须在您的领域上配置特定的实现,以匹配您用来散列用户凭据的散列算法。

例如,假设您的应用程序使用用户名/密码对进行身份验证。由于上述散列凭据的好处,假设您想在创建用户帐户时使用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 次哈希迭代以获得强大的安全性(请参阅HashedCredentialsMatcherJavaDoc 了解原因)。这里是 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需要 salt 才能对提交的内容执行相同的哈希技术,以AuthenticationToken查看令牌是否与您保存在数据存储中的内容匹配。因此,如果您对用户密码使用加盐处理(您应该!!!),请确保您的Realm实现通过返回SaltedAuthenticationInfo实例来表示。

禁用身份验证

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

当然,Realm如果你想验证 Subjects,至少一个配置需要能够支持 AuthenticationTokens。

领域授权

SecurityManagerPermission将或Role检查的任务委托给Authorizer,默认为ModularRealmAuthorizer。

基于角色的授权

当在 Subject 上调用重载方法 hasRoles 或 checkRoles 方法之一时

  1. Subject代表以SecurityManager识别是否分配了给定的角色
  2. SecurityManager然后委托给Authorizer
  3. 然后,授权方一一引用所有授权领域,直到找到分配给主题的给定角色。如果没有任何 Realm 授予 Subject given Role,则通过返回 false 来拒绝访问
  4. 授权 Realm AuthorizationInfo getRoles() 方法获取分配给 Subject 的所有角色
  5. 如果在 AuthorizationInfo.getRoles 调用返回的角色列表中找到给定角色,则授予访问权限。

基于许可的授权

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

  1. Subject将授予或拒绝权限的任务委托给 SecurityManager
  2. SecurityManager然后委托给授权者
  3. 然后,授权者一一引用所有授权者领域,直到授予其权限如果任何授权领域均未授予权限,则主体将被拒绝权限
  4. 授权领域执行以下操作以检查是否允许主题:
    1. 首先,它通过调用AuthorizationInfo上的 getObjectPermissions() 和 getStringPermissions 方法并汇总结果来识别直接分配给 Subject 的所有权限。
    2. 如果RolePermissionResolver已注册,它用于根据分配给 Subject 的所有角色检索权限,方法是调用RolePermissionResolver.resolvePermissionsInRole()
    3. 对于来自 a 的聚合权限。和 b。调用 implies() 方法来检查这些权限中的任何一个是否隐含了已检查的权限。请参见通配符权限