Shiro权限框架知识扩展

本文深入探讨Apache Shiro框架的认证与授权机制,解析Subject、Token、Realm及凭证匹配等核心概念,介绍如何利用IniRealm和JdbcRealm进行用户认证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.认证(Authentication)

    在Shiro中,认证指的是识别和证明操作者是一个合法用户。“用户”这个概念在框架中抽象为主体Subject的概念。用户如果想要通过认证,需要提供Principal(身份)和Credentials(凭证),从而应用能验证用户身份。这些身份和凭证信息,在Shiro框架中以Token口令的概念进行封装。Shiro中的认证Token接口AuthenticationToken中封装了这俩个信息。
在这里插入图片描述
    Principal: 身份,既主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/手机号。
    Credentials: 凭证/证明,既只有主体知道的安全值,如密码/数字证书等。

    身份和凭证是俩个比较抽象的概念,如果按简单项目中的概念说通俗一点,其实角色用户名/密码。以下是Token类的层次结构图
在这里插入图片描述

1.1基本的认证代码步骤在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.2记住我 vs 认证

    Shiro框架支持记住我,也就是短期内免登陆(或者说自动登陆)。虽然俩种都表示用户在认证环境被“放行了”,但我们要明确区分“记住我主体”和“认证主体”还是有一些区别的。

  1. “记住我主体”并不是匿名的,而是具有已知身份ID的,也就是subject.getPrincipals()不为空,但这个身份ID是来自于记住的上次认证的身份ID,如果是通过“记住我”实现的自动登陆,那么调用subject.isRemembered()返回true。
  2. "认证主体"指的是本次会话通过subject.login()等方法成功认证而且没有抛异常的Subejct对象的。一个真正的"认证主体"调用subject.isAuthenticated()返回true。

    注意,Subject的isRemembered()和isAuthenticated()是互斥的,一个为true意味着另外一个必然为fasle。为什么要区分这俩者呢?因为通过"记住我"通过认证环节,用户本次并没有提供凭证信息,并不是真正严格的认证。在很多软件系统中支持这个功能是为了大部分时候方便。但一些高度敏感的功能比如财务数据、付款等操作,还是会要求提供再次输入密码。比如手机上的支付宝和微信支付,都是自动登陆的,但付款时还是要输入支付密码。

1.3注销Log Out

    用户的注销需要调用Subject.logout()方法。该操作会让Shiro清楚身份信息并让当前Session失效。对于Web项目而言,同时还会通过响应Header让浏览器删除记住的C ookie。在注销后,当前主体又处于匿名状态,可以再次执行登陆操作。
    注意:由于Web项目的“记住我”通常基于浏览器Cookie实现,而注销后的删除Cookie命令又是通过响应给浏览器的HTTP Header来传达,因此在注销后强烈建议重定向到一个新的页面,这样失效的Cookie就真的失效了,否则用户可能从当前页面继续提交无效的Cookie,导致引发一些认证异常。

1.4认证的内部流程

在这里插入图片描述
    上图是框架内部认证的流程示意图。具体流程为:

  1. 应用程序代码调用Subject.login(token)
  2. Subject实例–通常是DelegatingSubject类型或其子类–在login的内部会调用securityManager.login(token),委托安全管理器 进行实例的认证。
  3. 安全管理器进一步调用authenticator.authenticate(token),把认证工姓委托给内部的认证器authenticator,它一般是ModularRealmAuthenticator类型的实例,该类型的认证器支持多个域(Realm)的认证。
  4. 如果应用程序配置了多个Realm,该认证器会启用认证策略对象 AuthenticationStrategy。通俗一点说,AuthenticationStrategy。就是在多个Realm都被执行后,对认证结果进行综合判断,决定认证结果,如果程序只有一个Realm,那么认证器会注解执行Realm,不会使用认证策略对象。
  5. 对于每个配置的Realm(都是一个认证域AuthenticatingRealm类或子类),在认证环节,Shiro都会判断是否支持当前Token,如果支持就会执行该Realm的getAuthenticationInfo方法,该方法中封装了真正的认证逻辑
    在这里插入图片描述

2.域(Realm)

    Realm是Shiro框架安全方面的数据源,用于提供用户、角色、资源和权限等方面的数据。打开框架源码中Shiro的jar包中realm的package,可以看到这一系列的Realm,在IDEA中转换为类图如下
在这里插入图片描述
    在最顶层的是一个抽象的Realm接口,该接口有一个最重要的getAuthenticationInfo抽象方法,就是规定了任何域都必须要实现认证
在这里插入图片描述
    这个类层次结果是逐层(增强) 的。如果你的项目只需要认证,而不需要针对功能、数据进行权限检查,那么使用认证域
AuthenticatingRealm即可。如果需要授权,那么要从AuthorizaingRealm类或它的子类选一个进行使用或扩展。比如前面的示例我们基础了AuthorizatingRealm,重写了认证、授权的方法。

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

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

    如果你采用ini文件进行Realm的配置,可以怎么写。这里配置了三个自定义的域对象,并按顺序分配给了Shiro的securityManager对象。
    认证器受安全管理器的委托执行认证时,先通过认证域的supports(token)方法,判断该Realm是否支持提交过来的Token对象,如果返回true,则继续。举个例子,你的软件 系统为了支持生物识别(指纹、虹膜、声音)的认证,自定义了一个 Realm,那么这个 Realm 明显是不能支持 UsernamePasswordToken 这种类型的 Token 的,因此认证器不会调用这个自 定义 Realm。由此可以看出,Realm 才是认证的真正执行者,而不是认证器。
认证 Realm 的 getAuthenticationInfo(token) 方法是真正执行认证逻辑的方法,一般而言 逻辑应该是这样的:

  1. 检查 Token 信息
  2. 根据身份信息(通常是 User Name)从数据源中查找账户数据
  3. 确认 Token 中封装的凭证(通常是 Password)和数据源中保存的一致
  4. 如果凭证一致,把账户数据封装为 Shiro 可识别的 AuthenticationInfo 对象返回出去
  5. 如果凭证不一致,抛出 AuthenticationException
    当然,以上是一个通常的抽象流程,具体的实现代码可以不同,或者做些其它事情比如 更新数据库、保存文件等等,都可以自由实现。

2.1凭证匹配与加密

在这里插入图片描述
    从认证域 AuthenticatingRealm 的类图可以看到,该类中有个 credentialsMatcher 属性, 也就是凭证匹配器对象。在 Realm 中获取到了账户数据后,会和用户提交的认证数据 Token 会一起,提交给凭证匹配器进行对比,这一般决定了认证是否成功。Shiro 中提供了常用的 凭证匹配器类型,看 org.apache.shiro.authc.credential 包中的相关类层次结构:
在这里插入图片描述
    如果你的系统需要实现特殊的匹配规则,可以继承这些匹配器中的某一个并重写。重写 的匹配器可以通过 ini 的方式配置、Spring XML 中或 Java 中注入给 Realm 对象。
    Java 方式:

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

    或者ini方式:
[main]


customMatcher = com.company.shiro.realm.CustomCredentialsMatcher
myRealm = com.company.shiro.realm.MyRealm
myRealm.credntialsMatcher = $customMatcher

    Shiro 的所有开箱即用的 Realm 实现默认使用简单凭证匹配器 SimpleCredentialsMatcher, 这意味着默认情况下密码的匹配只是简单的字符串比较。例如 Realm 的认证方法收到一个 UsernamePasswordToken,简单凭证匹配器会直接用 Token 中用户提交的明文密码和数据库 的密码进行比较。当然,该匹配器除了支持 String 类型的比较,还支持 char[]、byte[]、File 和 InputStream 等其它类型。

2.2哈希与盐

    相比于在数据库中存储明文密码,更好的方式是在把密码保存到数据源之前先进行单向 散列(Hash 算法)。散列值是不可逆的,即无法通过密文反推出明文,安全性较高,因此 Hash 算法在当前的软件安全领域被大量地使用。
    要想使用 Hash 加密,可以用 Shiro 提供的 HashedCredentialsMatcher。要在认证时使用 该匹配器,进行基于 Hash 加密的密码对比,需要将 Realm 中默认的 SimpleCredentialsMatcher 替换为 HashedCredentialsMatcher。
     加密领域的盐 Salt,不是炒菜的调料,而是为了提高加密的安全性。不管是对称加密, 还是非对称加密,在用户信息和算法可能被泄漏的情况下,都存在密码被反推出来的可能。 在加密环节如果加盐,等于多了一重安全因素。一般来说,盐就是一个不被外界知道的随机 字符串,把用户的明文密码加上盐,再进行加密得到密文(密码的加密后的形式)。
在这里插入图片描述
    也就是说最终的密文是以下两个内容的函数:
    1.用户输入的密码明文
    2.盐值

    用户进行注册时,第一次填写的密码,需要经过这个步骤进行加密,然后要将以下内容 存入数据库,以便后续登录时用来对比:
    1.用户名
    2.最终的密文
    3.加密所用的盐值

3.基于 IniRealm 进行认证

3.1.Shiro 的 ini 配置格式

    ini 文件是 Shiro 中比较常用的一种格式,他和 Java 的 properties 文件差不多,都是键值 对形式,主要一个优点就是多了 [节点] 的语法。Shiro 中规定的几个主要 ini 节点有 main、 users、roles、urls 等。
在这里插入图片描述
在这里插入图片描述
接下来,我们先以 ini 配置文件作为数据源,来编写一个基本的认证和授权测试。

  1. 编写 ini 配置文件
    在 resources/目录下新建一个 File,名为 shiro.ini。内容如下图。
    在这里插入图片描述
    在 users 节点下编写一个键值对,[jack=1234,admin] 的意思是身份为 jack 的用户, 第 154 页
    密码是 1234,属于 admin 角色。 在[roles]节点下编写一个键值对,[admin=user:delete,user:update]的意思是 admin 角色的用户具有 user:delete,user:update 这两个权限。
  2. 编写单元测试代码
    在这里插入图片描述

3.2基于 JdbcRealm 进行认证

前面提到 Realm 的层次结构和 IniRealm,其实对于以 RDBMS 作为数据源的软件项目来 说,使用 JdbcRealm 可能更加方便。打开 JdbcRealm 类的结构看看:
在这里插入图片描述
    从上面类图可以看到,使用 JdbcRealm 需要注入一个 DataSource,代表数据库。开发者还需要注入一些 SQL 语句给这个域对象,其中

setAuthenticationQuery 方法用来注入一个认证的 SQL,Shiro 据此查询用户数据,
setUserRolesQuery 方法用来注入一个查询用户、角色关联数据的 SQL,
setPermissionsQuery 方法用来注入一个查询权限数据的 SQL

    如果你不想这么麻烦,那么在设计这方面的数据库表时,可以继续遵循“约定大于 配置”的理念,都按 Shiro 默认表名、字段来设计。该类内置了一些列默认 SQL 语句, 例如你数据库中的用户表名为“users”且用户名字段名为“username”,那么就可以 不需要注入认证的 SQL。其它几个 SQL 同理。
    接下来,我们使用 JdbcRealm 实现从数据库查询,实现认证和基于角色的授权。

  1. 首先按照Shiro默认命名建立几个表
    在这里插入图片描述
  2. 编写shiro.ini配置并导入依赖

在这里插入图片描述
这里我们采用了业界很受欢迎的高性能数据库连接池——阿里巴巴的 druid 作 为 DataSource 的具体实现,因此需要导入 maven 依赖。
在这里插入图片描述

  1. 编写测试代码
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值