Files
rikako-note/spring/Apache Shiro/Apache Shiro Realm.md
2023-01-10 21:59:13 +08:00

8.2 KiB
Raw Blame History

Apache Shiro Realm

Realm简介

Realm是一个组件用来访问针对特定应用的安全数据例如user、role或permission。Realm负责将这些安全信息翻译为Shiro能够理解的格式。
由于大多数数据源都会同时存储authentication和authorization信息故而Realm能够同时执行authentication和authorization操作。

Realm配置

对于Realm的配置可以在ini文件中进行如下配置

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

; 如下指定的顺序会影响Authentication/Authorization过程中的顺序
securityManager.realms = $fooRealm, $barRealm, $bazRealm

Realm Authentication

支持Authentication

在Realm被询问去执行Authentication时首先会调用该Realm的supports方法如果supports方法的返回值是true时getAuthenticationInfo方法才会被调用。
通常情况下Realm会对提交的Token类型进行检测并查看当前Realm是否能对该类型Token进行处理。

处理AuthenticationToken

如果Realm支持该提交的Token类型那么Authenticator会调用Realm的getAuthenticationInfo方法该方法代表了通过Realm数据库来进行认证尝试。
该方法会按照如下顺序进行执行:

  1. 查看Token中存储的principals信息
  2. 根据Token中的principals在data source中查找对应的账户信息
  3. 确保提交Token中的credentials和data source中查找出的credentials相匹配
  4. 如果credentials匹配那么会将用户账户的信息封装到AuthenticationInfo中并返回
  5. 如果credentials不匹配会抛出AuthenticationException

credentials匹配

为了确保在credentials匹配的过程中该过程是可插入pluggable和可自定义的customizableAuthenticationRealm支持CredentialsMatcher的概念通过CredentialsMatcher来进行credentials的比较。
在从data source中查询到账户数据之后会将其和提交Token中的credentials一起传递给CredentialsMatcher由CredentialsMatcher来判断credentials是否相等。
可以通过如下方式来定义CredentialsMatcher的比较逻辑

Realm myRealm = new com.company.shiro.realm.MyRealm();
CredentialsMatcher customMatcher = new com.company.shiro.realm.CustomCredentialsMatcher();
myRealm.setCredentialsMatcher(customMatcher);

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

简单比较是否相等

Shiro中所有开箱即用的Realm其实现都默认使用SimpleCredentialsMatcherSimpleCredentialsMatcher简单会对存储在data source中的principals和提交Token中的credentials进行比较相等操作。

Hash Credentials

将用户提交的principals不做任何转换直接存储在data source中是一种不安全的做法通常是将其进行单向hash之后再存入数据库。
这样可以确保用户的credentials不会以raw text的方式存储再data source中即使数据库数据被泄露用户credentials的原始值也不会被任何人知道。
为了支持Token中credentials和data source中hash之后credentials的比较Shiro提供了HashedCredentialsMatcher实现可以通过配置HashedCredentialsMatcher来取代SimpleCredentialsMatcher。

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

指定HashedCredentialsMatcher

可以通过如下方式来指定特定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

如果制定了HashedCredentialsMatcher那么Realm.getAuthenticationInfo必须返回一个SaltedAuthenticationInfo实例而不是普通的Authentication实例。该SaltedAuthenticationInfo确保在创建用户信息时使用的salt可以在CredentialsMatcher中被使用。
HashedCredentialsMatcher在对Token中提交的credentials进行hash时需要使用到salt值来将用户提交的credentials进行和创建用户时相同的散列。

关闭Realm的Authentication

如果对某个Realm想要对该realm不执行Authentication可以将其实现类的supports方法只返回false此时该realm在authentication过程中绝对不会被询问。

Realm Authorization

SecurityManager将校验permission和role的工作委托给了Authorizer默认是ModularRealmAuthorizer。

基于role的authorization

当subject的hasRoles或checkRoles被调用其具体的执行流程如下

  1. subject将校验role的任务委托给SecurityManager
  2. SecurityManager将任务委托给Authorizer
  3. Authorizier会调用所有的Authorizing Realm直到该role被分配给subject。如果所有realm都没有授予subject该role那么访问失败返回false。
  4. Authorizing Realm的AuthorizationInfo.getRoles方法会获取所有分配给该subject的role
  5. 如果待检测的role在getRoles返回的role list中那么授权成功subject可以对该资源进行访问

基于permission的authorization

当subject的isPermitted或checkPermission方法被调用时其执行流程如下

  1. subject将检测Permission的任务委托给SecurityManager
  2. SecurityManager将该任务委托给Authorizer
  3. Authorizer会以此访问所有的Authorizer Realm直到Permission被授予。如果所有的realm都没有授予该subject权限那么subject授权失败。
  4. Realm按照如下顺序来检测Permission是否被授予
    1. 其会调用AuthorizationInfo的getObjectPermissions方法和getStringPermissions方法并聚合结果从而获取直接分配给该subject的所有权限
    2. 如果RolePermissionRegister被注册那么会根据subject被授予的role来获取role相关的permission根据RolePermissionResolver.resolvePermissionsInRole()方法
    3. 对于上述返回的权限集合implies方法会被调用用来检测待检测权限是否隐含在其中