Apache Shiro 核心原理及实践

简介

Shiro解决的核心问题

对常见的权限模块进行抽象,提炼通用的权限认证、授权信息等能力,认证信息可以选常见的存储组件存储,比如 redis 数据库。

核心构成

Shiro 的架构主要由三个核心组件构成:Subject、SecurityManager 和 Realm。

  1. Subject 代表了当前用户,绑定到当前线程
  2. SecurityManager 是安全管理器,是 Shiro 等控制器
  3. Realm 则是安全数据源,用于获取安全实体的认证信息和授权信息。

原理

如何抽象?

当你登录时,我们需要对身份进行校验,这个过程是认证

认证信息一般需要查询数据库,我们将这些动作封装在 Realm,简单理解为专门执行 DAO 相关动作。

当认证通过后,我们需要判断当前用户能否进行访问当前接口,这个过程是 授权

我们可以提供一个实体信息,封装当前用户信息,程序可以直接操作和使用,这个是 Subject

如果我们选择 Session 存储,通过抽象出 SessionManager 统一管理 Session 信息。

Session 存储底层我们可以选择缓存,比如 Redis,抽象 CacheManager 进行统一管理。

...

最后,我们再抽象一个控制器 SecurityManager,全局调度以上这些能力,如上,我们便完成了 Shiro 对权限能力的抽象提取。

Apache Shiro 核心抽象逻辑

哪些是抽象的逻辑?哪些是业务需要去实现的?

  1. SecurityManager:这是 Shiro 的核心,负责协调 Shiro 的其他组件。不需要实现它,只需要在配置 Shiro 时指定一个实例即可。
  2. Authenticator:负责 Subject 的认证。Shiro 提供了一些默认的实现,可以直接使用,也可以根据需要自定义。
  3. Authorizer:负责完成授权功能。Shiro 提供了一些默认的实现,可以直接使用,也可以根据需要自定义。
  4. SessionManager:如果使用了会话,SessionManager 管理会话的生命周期。Shiro 提供了一些默认的实现,可以直接使用,也可以根据需要自定义。
  5. CacheManager:Shiro 提供了缓存功能,可以直接使用,也可以根据需要自定义。
  6. Cryptography:Shiro 提供了一套简单易用的密码学工具,可以直接使用。

然后,有一些部分需要根据业务需求去实现的,例如:

  1. Realm:Realm 是一个特定的安全 DAO,它负责从数据源中获取安全实体,如用户的身份信息、角色、权限等。需要实现 Realm,以便 Shiro 可以从你的应用中获取这些信息。
  2. Subject:在 Shiro 中,Subject 是一个安全性的概念,它包含了当前用户的信息,如用户的身份信息、角色、权限等。需要根据业务需求来创建和管理 Subject。
  3. 用户的身份信息、角色、权限等:这些都是需要根据业务需求来定义和管理的。
  4. 登录和退出的逻辑:需要根据业务需求来实现用户的登录和退出逻辑

工作流程

一般来讲,Shiro 利用服务端存储 session 信息,登录时进行身份认证、授权,并将这些信息存储在服务端(比如 redis),然后返回 sessionId 给前端,前端每次请求都带上 sessionId。

请求到达时,Shiro 的 SessionManagementFilter 过滤器检测 sessionId,并加载对应的 session 信息,然后初始化 Subject 信息,最终将 Subject 绑定到当前线程中。

当前请求线程处理完毕后,清理掉当前线程的 Subject 信息。

0. 登录

主要步骤:

  1. 收集用户身份/凭证:通常是在用户登录的时候收集。如用户名/密码,或者在实现单点登录(single sign-on)时,可能是一个交换的 token。身份可以是任何东西,但凭证通常是敏感的(如密码),所以最好在可能的情况下尽快获取并销毁凭证。
  2. 构建 AuthenticationToken:Shiro 通过 AuthenticationToken 接口获取用户的身份/凭证,该接口有一个实现类 UsernamePasswordToken,可以使用它来获取用户名/密码。
  3. 获取 Subject:在 Shiro 中,Subject 是一个安全性的概念,包含了当前用户的所有安全操作。可以通过 SecurityUtils.getSubject() 来获取。
  4. 登录:通过调用 Subject 的 login 方法来登录。

示例:

java 复制代码
// 收集用户身份/凭证,通常是在用户登录的时候收集
String username = "admin";
String password = "123456";

// 构建 AuthenticationToken
AuthenticationToken token = new UsernamePasswordToken(username, password);

// 获取 Subject
Subject currentUser = SecurityUtils.getSubject();

// 登录
try {
    currentUser.login(token);
    // 登录成功
} catch (AuthenticationException ae) {
    // 登录失败
}

1. 请求到达

当一个请求到达应用时,它首先会被 Shiro 的过滤器链处理。

Shiro提供了一系列的过滤器,如身份验证过滤器、授权过滤器等,可以根据需要配置这些过滤器。

上一步我们实现了登录,并将登录信息存储到 Session 中,当请求到达时,服务端要尝试获取 session 信息,并创建 Subject 实体,提供给整个请求生命周期使用,负责处理整个动作的是 SessionManagementFilter

  1. 会话管理器会从请求中获取 sessionId,然后使用这个 sessionId 来查找对应的会话。如果找到了对应的会话,会话管理器就会返回这个会话,否则,它会创建一个新的会话。
  2. 当 SessionManagementFilter 获取到一个会话后,它会将这个会话存储在当前线程的 Subject 实例中。这样,就可以在当前线程的任何位置通过 SecurityUtils.getSubject() 获取到这个 Subject 实例,并从中获取到会话信息。

2. 身份验证

如果请求需要身份验证,Shiro 会使用配置的 SecurityManager 和 Realm 来完成身份验证。

具体来说,Shiro 会创建一个 AuthenticationToken,包含用户提供的身份(如用户名)和凭证(如密码),然后通过 SecurityManager 将这个 AuthenticationToken 传递给 Realm

Realm 会使用这个 AuthenticationToken 来从数据源中获取相应的身份信息和凭证,然后比较它们是否匹配。

3. 授权

如果请求需要授权,Shiro 会使用配置的 SecurityManager 和 Realm 来完成授权。

具体来说,Shiro 会创建一个 AuthorizationInfo,包含用户的角色和权限,然后通过SecurityManager 将这个 AuthorizationInfo 传递给Realm。

Realm 会使用这个 AuthorizationInfo 来从数据源中获取相应的角色和权限,然后比较它们是否匹配。

4. 执行操作

如果身份验证和授权都成功,Shiro 会继续处理请求,执行相应的操作。

5. 会话管理

在整个过程中,Shiro 还会管理用户的会话。

Shiro 提供了一套会话管理 API,可以使用它来获取和操作用户的会话。

6. 退出

当用户完成操作并退出应用时,Shiro 会清理用户的会话和身份信息。

多个 realm?

在 Apache Shiro中,可以配置多个 Realm,以便从不同的数据源获取用户的身份信息、角色、权限等。

例如,可能有一个 Realm 用于从 MySQL 数据库中获取用户信息,另一个 Realm 用于从 第三方服务中获取用户信息。

当配置了多个 Realm 时,Shiro 的 SecurityManager 会按照它们在配置中的顺序来调用它们。当一个 Realm 无法处理一个身份验证或授权请求时,SecurityManager 会将请求传递给下一个 Realm。

身份验证

对于身份验证(Authentication),Shiro 会遍历所有配置的 Realm,只要有一个 Realm 能够验证成功,那么整个身份验证就会成功。

如果所有 Realm 都无法验证成功,那么身份验证就会失败。

授权

对于授权(Authorization),Shiro 会收集所有 Realm 的授权信息,然后进行聚合。

例如,如果有多个 Realm,Shiro 会将所有 Realm 的授权信息(角色、权限等)进行合并,然后基于这些合并后的授权信息来进行授权决策。

所以,对于身份验证,任意一个 Realm 能处理就行;对于授权,Shiro 会考虑所有 Realm 的授权信息

实践

最小可用的 shiro 实战:

  1. 添加Shiro依赖:在你的项目中添加 Shiro 的依赖。如果使用的 Maven,可以在 pom.xml 文件中添加依赖:
xml 复制代码
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.7.1</version>
</dependency>
  1. 创建Realm:创建一个自定义的 Realm,用于从数据源中获取用户的身份信息、角色、权限等。以下是一个简单的 Realm 实现:
java 复制代码
public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 在这里完成授权信息的获取
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 在这里完成认证信息的获取
        return null;
    }
}
  1. 配置SecurityManager:创建一个 DefaultSecurityManager 实例,并为其设置你创建的 Realm:
java 复制代码
MyRealm myRealm = new MyRealm();
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(myRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
  1. 登录:创建一个 Subject 实例,并使用它来完成登录:
ini 复制代码
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
subject.login(token);
  1. 授权:一旦 Subject 被认证(登录成功),就可以使用它来完成授权,即确定 Subject 是否有权限执行某个操作:
less 复制代码
if (subject.hasRole("admin")) {
    // 如果Subject有"admin"角色,执行相应的操作
}
  1. 退出:当 Subject 完成操作并退出应用时,可以使用它来完成退出:
ini 复制代码
subject.logout();
相关推荐
机器之心1 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴1 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲2 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
机器之心2 小时前
AAAI 2025|时间序列演进也是种扩散过程?基于移动自回归的时序扩散预测模型
人工智能·后端
hanglove_lucky3 小时前
本地摄像头视频流在html中打开
前端·后端·html
皓木.4 小时前
(自用)配置文件优先级、SpringBoot原理、Maven私服
java·spring boot·后端
i7i8i9com4 小时前
java 1.8+springboot文件上传+vue3+ts+antdv
java·spring boot·后端
秋意钟4 小时前
Spring框架处理时间类型格式
java·后端·spring
我叫啥都行5 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
Stark、5 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端