Apache Shiro Realm(五)

与用户安全相关的数据,如Users、Roles、Permissions等信息可能位于各种数据源中,如文件系统、LDAP、各种数据库等。Shiro中的Realm相当于是DAO层,对于低层的数据源而言,它调用相应的接口API获取数据,对于Shiro中的其它组件如Authenticator与Authorizer而言,Realm为它们提供统一的接口,Realm作用就是屏蔽不同的数据源,为上层的组件提供统一的接口。

1、Realm的配置

这个话题前边已讲说过了。首先Shiro支持多Realm,在身份认证与授权的过程中,Realm生效的顺序会影响到结果,而Realm生效的顺序是在配置文件中决定的。有两种,一种是显式顺序,如下:

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

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

有三个Realm,它们生效的顺序取决于最后一行代码。

另一种是隐式顺序,如下:

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

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

最后一行被注释了,那么这个时候默认的生效顺序就是它们出现在配置文件中的顺序。

2、Realm与Authentication

看一下单个Realm与Authentication组件之间具体发生了那些事情。

2.1.是否支持AuthenticationTokens

首先Subject会将用户提交的Principal、Crendential转换成某种类型的AuthenticationTokens实现。AuthenticationTokens可以有多种实现,有的转换的可能是“用户名+密码”,有的可能转换的是“用户名+密码+验证码”,也有可能是“手机号+验证码等”很多很多。
Authenticator组件在使用某个Realm时,首先调用它的support方法,并将AuthenticationTokens传进去,Realm用instance of确认一下AuthenticationTokens的类型,比如Subject携带的是“用户名+密码”的AuthenticationTokens,而当前Realm只支持“手机号+验证码”的AuthenticationTokens,这个时候当前Realm就会返回false,Authenticator就会将当前Realm忽略,转而去查询其它的Realm。AuthenticationTokens其实在多Realm的情况下,起到过虑Realm的作用。

2.2.处理支持的AuthenticationTokens

如果当前Realm支持传入的AuthenticationTokens实例的类型,则后续的认证处理将会展开,Authenticator将会调用当前Realm的getAuthenticationInfo(token)方法,这个方法大致执行如下几个步骤:

  1. 检查token中的Principal,比如用户名,首先要确认这个用户名在数据源中是否存。
  2. 根据Principal从数据源获取其它内容,比如Principal是用户名,那么就用这个用户名查询相对的password。
  3. 将token中的Crendential与数据源中Crendential进行对比。
  4. 如果匹配,AuthenticationInfo实例被返回,里边封装用户相关数据。
  5. 如果不匹配就抛出异常。

对于开发者自定义的Realm,方法getAuthenticationInfo(token)只要最终返回AuthenticationInfo表示成功,抛出异常表示失败就可以了,内部实现细节由开发者自己决定。

提示:自定义的话一般从AuthorizingRealm抽象类派生,而不是全部从头开始,里边的默认实现可以节约开发者时间。

2.3.Crendential的匹配

在比对Crendential这一步中,不同的应用,逻辑可能是不同的。数据源中的Crendential可能是加秘的或者用什么算法编过码,用户提交的Crendential可能是加过盐的。因此在比对之前,Realm要分别对来自安全数据源的数据与用户提交的数据进行各种解秘、净化、转码之类的工作。因此Realm有一个CredentialsMatcher类型的接口,专门负责处理这件事情。用户可以实现自己的CredentialsMatcher并配置,如下:

[main]
...
customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
myRealm = com.company.shiro.realm.MyRealm
myRealm.credentialsMatcher = $customMatcher
...

2.3.1.明文对比

以前的文章中提到过,Shiro内置了多种CredentialsMatcher的实现,比如HashedCredentialsMatcher、Md5CredentialsMatcher、Sha512CredentialsMatcher等。

Realm默认的CredentialsMatcher实现是SimpleCredentialsMatcher实现,只是对比用户提交的Crendential与安全数据源的Credential是否相同,不进行任何转换。

2.3.2.HASH运算

实际应用中,不能将用户的Crendential如password等直接明文保存在数据源中。一种常用的方法是将用户密码进行HASH运算,然后再将结果保存到数据源中。对于用户提交的password,也同样进行相同的运算,然后再与数据源中的HASH值进行对比。这样子除了用户,没有人知道他的密码是什么,因为数据源中保存的是HASH值,而由HASH值不可以逆向推导出用户的密码。但是如何用户的密码太简单太短的,有可能会被暴力破解。因此可以对用户的原始密码加盐与多重HASH计算提高暴力破解的难度。

当然对于用户登录时提交的凭证也要进行一样的运算,再去比对。下面是一个例子:

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

上边代码演示的是如何创建用户,对用户密码加盐,HASH运算、编码最后保存的例子。

首先生成一个随机数当作是“盐”。
然后将用户的原始密码、生成的“盐”进行Sha256Hash运算,后边的1024表示迭代次数
然后进行base64编码。
最后在数据库中保存用户信息,包括刚才的盐。

这样的话,即使用户的密码很短很简单,并且用户的所有数据都泄露了,拿到这些数据的人也很难计算出用户的原始密码,这个需要庞大的计算力。

接下来看Shiro的Realm怎么配:

[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
...
  1. 在上边创建用户的例子了,为密码加了盐,在实际开发中,盐与用户信息往往保存在不同的地方,单纯的盐泄露与用户信息泄露都是安全的,提高安全度。所以对于自定义的myRealm,要实现里边的SaltedAuthenticationInfo接口,自定义获得盐的方式。

3、Disabling Authentication

指禁止掉某个Realm的身份认证功能,原因可能只是想将某个Realm当成授权处理的数据源。只要让这个Realm的support方法一直返回false就可以了。

4、Realm与Authorization

基于角色的授权:

  1. Subject将授权请求托管给SecurityManager
  2. SecurityManager再将请求托管给Authorizer组件。
  3. Authorizer逐个查找Realm,直到在某个Realm中找到匹配的角色,并返回true。如果查找了全部Realm以后也没有为当前Subject找到Role,则返回false。
  4. 对于每个Realm,它自己要从自己的数据源返回当前Subject的所有Role
  5. 如果没有角色,或者有角色但不匹配,返回false,否则返回true。

基于权限的授权:

  1. Subject将授权请求托管给SecurityManager
  2. SecurityManager再将请求托管给Authorizer组件。
  3. Authorizer逐个查找Realm,直到在某个Realm中找到匹配的权限,并返回true。如果查找了全部Realm以后也没有为当前Subject找到匹配的权限,则返回false。
  4. 对于Realm则执行如下步骤:
    a.通过调用getObjectPermissions()方法获取Subject的全部权限,然后调用AuthorizationInfo的getStringPermissions方法聚合返回结果。
    b.如果Realm被配置了RolePermissionResolver,则调用其RolePermissionResolver.resolvePermissionsInRole()方法,这个方法将与Subject对应的所有Role的所有Permission转换成标准格式。
    c.调用 implies()方法检查权限,参考WildcardPermission

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值