Apache Shiro Realm 介绍--官网

Apache Shiro Realms 介绍

Realm 是负责获取应用程序安全相关的数据(如用户,角色,权限),并将其转化为Shiro理解的格式的组件,正是因为它,Shiro才可以提供一个统一的,好用的Subject编程API,而不需要关心存储安全相关数据的数据源是什么形式的

通常Realm和不同的数据源(关系型数据库,LDAP,文件系统等)之间是一对一的关系,Realm的实现会使用各个数据源特定的API来获取数据,如JDBC,JPA,文件 IO等等

Realm本质上就是专用于安全方面的DAO

应用程序一般会把认证数据和授权数据存储在同一个数据源之中,因此,Realm拥有认证和授权两个功能

Realm 配置

如果使用的是Shiro的INI配置,可以像其他组件那样在 [main] 小节中进行配置,但是有两种不同方式

显式配置

可以直接给SecurityManagerrealms赋值,realms是一个集合类型的属性

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

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

这种方式配置后,Realm的顺序是确定的,在认证和授权的过程中,SecurityManager将会按照这个顺序与所有的Realm进行交互,这个顺序是很重要的,详情看认证章节中的认证流程小节

隐式配置

不推荐使用,隐式配置中,Realm的顺序取决于各个Realm出现的顺序,或许不经意间修改了Realm的顺序,使得认证和授权过程中出现问题,容易出错。在以后的版本中将会删除这种隐式配置

Shiro可以智能的检测所有配置的Realms,并将它们交给securityManager,这种方式将会让以Realm被定义的顺序作为以后SecurityManager与其交互的顺序,如下面的例子:

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

然而,这种方式不推荐使用,原因在前面,更推荐采用显式配置的方式,因为它有一个确定的顺序

Realm 认证

现在来了解一下当AuthenticatorRealm交互时发生了什么

检查是否支持AuthenticationToken

正如在认证那一章节所描述的,在一个Realm执行认证之前,将会调用它的supports方法来检查传过来的AuthenticationToken是否是这个Realm能够处理的类型。只有当它返回true时,才会调用它的getAuthenticationInfo(token) 方法

处理AuthenticationToken

如果Realm能够处理传过来的AuthenticationToken,那么Authenticator将会调用RealmgetAuthenticationInfo(token)方法,该方法从后端数据源中找到相关的认证信息同时进行认证。其流程为:

  1. AuthenticationToken中获取需要认证的用户principal,即账号
  2. 基于上面的principal,从数据源中找到相关的账户信息
  3. AuthenticationToken中的credentials与存储在数据源中的进行对比,即密码匹配
  4. 如果匹配的上,将会将账户信息封装为一个AuthenticationInfo实例返回给Shiro
  5. 如果没匹配上,则会抛出一个AuthenticationException

以上流程是getAuthenticationInfo方法的概括,实际上,Realms可以做任何你想在其中做的事,比如日志记录等等。如果creadentials匹配无误,那么将会返回一个非空的AuthenticationInfo实例供Subject使用

直接实现一个Realm接口是比较复杂且困难的,因此Shiro提供了一个AuthorizingRealm抽象类,它实现了一个通用的认证和授权的工作流程,可以继承它来自定义Realm以节约时间

Credentials 匹配

如果按照上面的描述,Realm将负责校验Subject提交的credentials(如密码)是否与存储在数据源中的一致,如果它们匹配的上,那么认证成功

注意:credentials 匹配是Realm的职责,而不是Authenticator的职责。Realm知道credentials的格式,可以执行详细的credentials匹配,而Authenticator只是一个通用的工作流的组件

credentials的匹配在所有的应用程序中都几乎一致,而只有详细的数据比对时会有差别,因此为了让这个过程可定制化,AuthenticatingRealm及其子类都支持CredentialsMatcher的概念,它专门用来执行credentials比对逻辑

从数据源中找到相关账号信息以后,这些信息将会和Subject提交的AuthenticationToken一起交给CredentialsMatcher来进行credentials的比对

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会使用SimpleCredentialsMatcher,它进行的仅仅是一个简单的文本匹配,比如提交的是一个UsernamePasswordToken,那么SimpleCredentialsMatcher将仅仅比对token中的credentials和数据源中存储的credentials是否相等

SimpleCredentialsMatcher不仅可以比对字符串,也可以比对字符数组,字节数组,文件和InputStreams等

散列的credentials

目前更安全的方式是将用户的credentials进行单向散列处理后,存储到数据源中,这确保了用户credentials的安全性,没人能知道它的原始值是多少

为了支持这种方式,Shiro提供了HashedCredentialsMatcher来进行credentials比对。相关详细信息可以查看HashedCredentialsMatcher文档

散列和比对

Shiro提供了各种HashedCredentialsMatcher实现,我们需要指定使用哪个实现类来对你的应用程序中的用户credentials进行散列

假设如下场景:你的应用程序使用用户名/密码来进行认证,你将使用SHA-256算法来对用户密码进行单向散列,然后存储到数据源中,那么你可能会使用如下的代码

import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
...
// 这里使用随机数生成器来生成盐
// 正常应用将会专门用一个属性来作为盐
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();

// 现在我们使用这个随机生成的盐,多次迭代,然后得到一个base64编码的值
String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt, 1024).toBase64();

User user = new User(username, hashedPasswordBase64);
// 将盐保存在账号信息中,HashedCredentialsMatcher也会使用这个盐来进行密码比对
user.setPasswordSalt(salt);
userDAO.create(user);

因为采用的是SHA-256算法对密码进行了加密,因此也需要告诉Shiro使用哪个HashedCredentialsMatcher来进行密码比对。这个例子中,我们使用的是随机盐,执行了1024次迭代,因此,相关配置如下:

[main]
...
credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# base64 encoding, not hex in this example:
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
# 下面这个属性仅仅在Shiro1.0需要,在1.1及之后的版本中移除了(文档上的描述应该是错的)
credentialsMatcher.hashSalted = true

...
myRealm = com.company.....
myRealm.credentialsMatcher = $credentialsMatcher
...
SaltedAuthenticationInfo

最后需要注意的是,这里Realm必须返回一个SaltedAuthenticationInfo的实例。SaltedAuthenticationInfo接口确保了你使用的盐能被HashedCredentialsMatcher感知到

HashedCredentialsMatcher需要同样的盐,对Subject提交的AuthenticationToken执行同样的加密算法才能知道是否匹配。因此,如果对用户密码在存储时进行了加盐加密,那就需要Realm在认证时返回一个SaltedAuthenticationInfo的实例

禁用认证

如果你不想让Realm执行认证的逻辑(或许仅仅只想要Realm执行授权的逻辑),可以通过修改supports方法,让其直接返回一个false,即可禁用掉该realm的认证功能。当然,应用程序中至少需要一个Realm得负责认证

Realm 授权

SecurityManager会将检查权限和角色的任务交给Authorizer组件,默认是ModularRealmAuthorizer

基于角色的授权

SubjecthasRoles()方法或checkRoles()方法被调用时:

  1. Subject将需要的Role交给SecurityManager进行检查
  2. SecurityManager会把工作交给Authorizer
  3. Authorizer会遍历所有的Authorizing Realms,调用其hasRoles()方法和checkRoles()方法,直到有Realm返回true时,权限检查通过。否则就是没有权限
  4. Authorizing Realm会通过 AuthorizationInfo的getRoles() 方法来获取Subject拥有的所有角色
  5. 如果在AuthorizationInfo.getRoles()返回的Role列表中包含需要的Role,则允许访问

基于权限的授权

SubjectisPermitted()方法或checkPermission()方法被调用时

  1. Subject将委托SecurityManager执行权限检查
  2. SecurityManager 委托给Authorizer
  3. Authorizer会遍历所有的Authorizing Realm,调用其isPermitted()或checkPermission()方法,直到有Realm返回true时,权限检查通过。否则Subject就是没有权限
  4. Authorizing Realm会以下面的流程来检查权限:
    1. 调用AuthorizationInfogetObjectPermissions()getStringPermissions()先获取所有Subject拥有的权限并聚合结果
    2. 如果注册了RolePermissionResolver,则它也会被用于遍历该Subject所有基于角色的权限,通过调用RolePermissionResolver.resolvePermissionsInRole()
    3. 遍历上面两步得到的权限,调用其implies()方法,检查是否其隐含了我们正在检查的权限。如"user:*"隐含了"user:delete"权限
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • Apache Shiro Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权...
    罗志贇阅读 3,214评论 1 49
  • Apache Shiro 认证 身份验证,即证明用户证明确实是他们自己。为了让用户证明自己的身份,需要他们提供一些...
    szn0108阅读 1,525评论 1 1
  • Shiro主要用来进行权限管理。简单的介绍如下: 一、概念 Shiro是一个安全框架,可以进行角色、权限管理。 S...
    指针与内存栈阅读 448评论 0 0
  • shiro安全控制目录 Realm充当了Shiro框架和应用的安全数据之间的连接器,也就是说,当我们与应用的安全数...
    小胖学编程阅读 1,534评论 0 3
  • 前言 这几天因为项目要用到鉴权,很久以前就听说过shiro了,但是之前一直要求用的是Spring Security...
    Martain阅读 1,313评论 0 3