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();
相关推荐
爱上语文35 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people38 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政6 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
拾光师8 小时前
spring获取当前request
java·后端·spring
Java小白笔记9 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis
JOJO___11 小时前
Spring IoC 配置类 总结
java·后端·spring·java-ee
白总Server12 小时前
MySQL在大数据场景应用
大数据·开发语言·数据库·后端·mysql·golang·php
Lingbug13 小时前
.Net日志组件之NLog的使用和配置
后端·c#·.net·.netcore
计算机学姐13 小时前
基于SpringBoot+Vue的篮球馆会员信息管理系统
java·vue.js·spring boot·后端·mysql·spring·mybatis
好兄弟给我起把狙13 小时前
[Golang] Select
开发语言·后端·golang