Realm 是可以访问程序特定的安全数据如用户、角色、权限等的一个组件。Realm 会将这些程序特定的安全数据转换成一种 Shiro 可以理解的形式,Shiro 就可以依次提供容易理解的 [Subject](https://www.ctolib.com/docs/sfile/apache-shiro-reference/IV. Auxiliary Support 辅助支持/14. Custom Subjects 自定义 Subject.md) 程序API而不管有多少数据源或者程序中你的数据如何组织。
前言
在 shiro 框架的文章中,了解到Realm
是用作获取用户身份信息和权限信息的。shiro 将这块分离出来,使得我们可以自定义,以适应不同的场景。比如用户的信息可以存储数据库中,也在外面添加了一层缓存,我们可以实现自己的Realm
,完成缓存和数据库的读取操作。
Realm Configuration
ini配置方式中Realm 在 secrityManager上的配置有两种方式:明确方式和隐含方式。不过一般不使用该方式
1 | fooRealm = com.company.foo.Realm |
Realm 接口
先来看看Realm
接口的定义,它的方法只有三个,涉及到了AuthenticationToken
和AuthenticationInfo
两个概念。
1 | public interface Realm { |
身份凭证
AuthenticationToken
是认证的凭证,可以简单的看作是账户名和密码。
1 | public interface AuthenticationToken extends Serializable { |
下面是AuthenticationToken
相关接口的类图
HostAuthenticationToken
添加了客户端的ip接口,RememberMeAuthenticationToken
添加了是否需要记住我的接口。我们接着需要看看子类的实现,UsernamePasswordToken
和BearerToken
。
UsernamePasswordToken
是用于账户名和密码的验证方式,所以它会保存这两个属性,当作Principal
和Credentials
。
1 | public class UsernamePasswordToken { |
BearerToken
是用于基于 token 的验证方式,所以它的Principal
和Credentials
都是 token。当使用 jwt 验证时,就可以使用BearerToken
。
1 | public class BearerToken { |
如果一个Realm支持提交的验证令牌,验证将调用 Realm 的getAuthenticationInfo(token) 方法,这是Realm 使用后台数据进行验证的一次有效尝试,顺序执行以下动作:
1.检查主要 principal (身份)令牌(用户身份信息);
2.基于主要 principal (信息),在数据源中查找对应的用户数据;
3.确定令牌支持的 credentials (凭证数据)和存储的数据相符;
4.如果凭证相符,返回一个AuthenticationInfo实例,里面封装了 Shiro 可以理解的用户数据。
5.如果证据不符,抛出 AuthenticationException异常。
这是所有Realm getAuthenticationInfo 实现的最高级别工作流,Realm 在这个过程中可以自由做自己想做的事情,比如记录日志,修改数据,以及其他,只要对于存储的数据和验证尝试来讲是合理的就行。
仅有一件事情是必须的,如果 credentials (凭证)和给定的 principal (主要信息)匹配,需要返回一个非空的 AuthenticationInfo 实例,用来表示来自数据源的 Subject 账户信息。
节约时间
直接实现 Realm 接口也许需要时间并容易出错,大部分用户选择继承 AuthorizingRealm 虚拟类,这个类实现了常用的认证和授权工作流,这会节省你的时间而且不易出错。
Simple Equality Check 简单证明匹配
所有 Shiro 的开箱即用 Realm 默认使用一个 SimpleCredentialsMatcher, SimpleCredentialsMatcher 对存储的用户凭证和从 AuthenticationToken 提交的用户凭证直接执行相等检查。
例如,如果提交了一个UsernamePasswordToken,SimpleCredentialsMatcher 检查提交的密码与存储的密码是否完全相等。
SimpleCredentialsMatcher 不仅仅对字符串执行相同对比,它可以对大多数常用类型,如字符串、字符数组、字节数组、文件和输入流等执行对比,查看 JavaDoc 获取更多的信息。
Hashing Credentials 哈希凭证:
取代将凭证按它们原始形式存储并执行原始数据的对比,存储终端用户的凭证(如密码)更安全的办法是在存储数据之前,先进行 hash 运算。
这确保终端用户的凭证不会以他们原始的形式存储,没有人能知道其原始值。与明文原始比较相比这是一种更为安全的做法,有安全意识的程序会更喜欢这种方法。
要支持这种加密的 hash 策略,Shiro 为 Realm 配置提供了一个HashedCredentialsMatcher 实现替代之前的 SimpleCredentialsMatcher。
Hashing 凭证以及 hash 迭代的好处超出了该 Realm 文档的范围,可以在HashedCredentialsMatcher JavaDoc更详细地了解这些主要内容。
Hashing and Corresponding Matchers 哈希以及相符合的匹配
对于一个使用 Shiro 的程序,如何配置才能简单地做到这些?
Shiro 提供了多个 HashedCredentialsMatcher 子类实现,你必须在你的 Realm 上配置指定的实现来匹配你的凭证所使用的 hash 算法。
例发,假设你的程序使用用户名/密码对来进行验证,基于上述 hash 凭证的好处,你希望当创建用户时以 SHA-265 方式加密用户的密码,你可以加密用户输入的明文密码并保存加密值:
1 | public class SHA256Util { |
1 | // /在新帐户保存盐。该 HashedCredentialsMatcher |
由于你使用 SHA-256 加密你的密码,你需要告诉 Shiro 使用相应的 HashedCredentialsMatcher 来检查你的 hashing 值,在这个例子中,我们为了加强安全创建了一个随机的 salt 并且执行 1024 Hash 迭代(查看HashedCredentialsMatcher JAVADoc了解为什么),下面的Shiro INI 配置来做这件工作。
1 |
|
确保正常运行的最后一件要做的事情是你的Realm实现必须返回一个 SaltedAuthenticationInfo 实例而不是普通的AuthenticationInfo,SaltedAuthenticationInfo 接口确保你在创建用户帐户时使用的salt(如上面调用的 user.setPasswordSalt(salt);)能被 HashedCredentialsMatcher 引用。
HashedCredentialsMatcher 需要使用 salt 来对提交的 AuthenticationToken 执行相同的 hashing 技术来对比提交的令牌是否与存储的数据相匹配,所以如果你对用户密码使用 salting(你应该这么做),确保你的 Realm 实现在返回 SaltedAuthenticationInfo 实例时引用它。
1 |
|
Disabling Authentication 禁用
如果有理由,你不希望某个 Realm 对某个资源执行验证(或者因为你只想 Realm 去执行授权检查),你可以完全禁用 Realm 的认证支持,方法就是在 Realm 的 supports 方法中始终返回 false,这样,你的 Realm 将在整个验证过程中不再被使用。
当然如果你想验证 Subject,至少要配置一个支持 AuthenticationTokens 的 Realm。
身份认证结果
AuthenticationInfo
表示身份认证后的结果,包含了用户的身份信息。需要注意下getPrincipals
返回的是身份集合,因为当存在多个Realm
时,一个账户有可能是有多个身份的。
1 | public interface AuthenticationInfo extends Serializable { |
AuthenticationInfo
的相关类图如下所示,注意到右上边的AuthorizationInfo
接口,它包含了用户的权限。
SaltedAuthenticationInfo
支持加盐操作,MergableAuthenticationInfo
支持合并用户身份信息。
SimpleAuthenticationInfo
也只是简单的实现,保存了对应的属性。
SimpleAccount
实现了AuthenticationInfo
接口,保存了身份信息,也实现了AuthorizationInfo
接口,保存了用户的权限。
Realm 子类
现在可以回过来看看 Realm 的子类,首先来看看它的类图
CachingRealm
提供了缓存管理,在后续子类中会用到。
AuthenticatingRealm
实现了身份认证。
AuthorizingRealm
实现了权限检查。
由于CachingRealm
类比较简单,这里就不做详细介绍了,对于AuthenticatingRealm
和AuthorizingRealm
需要详细介绍。
我们先来仔细看看AuthenticatingRealm
的原理,它负责返回用户的身份信息,并且会将结果缓存。
1 | public abstract class AuthenticatingRealm extends CachingRealm implements Initializable { |
继续看AuthorizingRealm
的原理,它实现了Authorizer
接口,负责返回用户的权限,并且也缓存结果。
1 | public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer { |
系统自带 Realm
shrio 已经实现了多个 Realm 的子类,如下图所示。
这些子类按照使用场景分为三类,左边以SimpleAccountRealm
为父类的,表示用户信息都存储到内存里。TextConfigurationRealm
表示从文件中加载信息到内存,它的子类IniRealm
表示读取ini
格式的配置文件,PropertiesRealm
表示读取properties
格式的配置文件。
中间以JdbcRealm
为父类的,表示用户信息存储在数据库中。它的子类SaltAwareJdbcRealm
在其基础之上,增加了盐,可以看作是一种加密手段。
右边以AbstractLdapRealm
为父类的,表示用户信息存储在ldap
服务中。ActiveDirectoryRealm
表示采用了ActiveDirectory
服务,它是ldap
实现的一种。
自定义 Realm
上面介绍了Realm
的设计思想,这里会介绍我们该如何使用Realm
。我们在自定义Realm
时,只需要继承AuthorizingRealm
,然后实现它的两个方法即可。
1 | class AuthorizingRealm { |
这里写个简单的例子,只允许admin
用户登录,它只有read
权限。
1 | class MyRealm extends AuthorizingRealm { |
来源
1 | https://www.ctolib.com/docs/sfile/apache-shiro-reference/II.%20Core%20%E6%A0%B8%E5%BF%83/6.1.%20Permissions%20%E6%9D%83%E9%99%90.html |
- 本文作者: 初心
- 本文链接: http://funzzz.fun/2021/02/03/Apache Shiro --- 7 realm/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!