Shiro

目录

导读

阅读人群

术语

介绍

使用Shiro可以做什么?

功能

主要的

附加的

架构

简单的架构图

详细的架构图

认证

认证的步骤

访问控制

授权三要素

权限

角色

用户

访问控制的方式

基于编程的方式

基于注解的方式

前端标签库

访问控制的执行顺序

配置全局权限解析器 

配置全局角色权限解析器 

领域 Realm

领域认证

执行顺序

构造Realm 

凭据匹配

领域访问控制

基于角色的访问控制

基于权限的访问控制

会话管理

使用会话

会话管理器

会话超时

侦听器

会话存储

Web的支持

其他功能

缓存

主体

Spring集成

环境配置

Shiro依赖

完整依赖

源码解析

登录流程解析


导读


        本文用来记录学习Shiro安全框架,初衷是尽可能的小而全,短而精。但是文笔有限,建议看完本文以后阅读官方文档伴读。文档内容基本来自官网加上一点点一些自身理解,希望不要误导你。文中学习Shiro的重点是与Spring进行集成,如果你想看关于ini配置文件的内容请查阅官方文档。

阅读人群

        如果你没有接触过Shiro或者其他安全框架建议从头部根据顺序看起,这样可能更容易理解。建议先了解相关术语,方便后面的阅读。举了一些例子用于说明这些术语可能不恰当。

        如果你已经对Shiro有过一些了解可以根据目录接口选看。

        如果你对Shiro足够了解,希望可以找到一些代码片段或者是想复制代码可以进入仓库(代码还没有写完无法跳转)。或者目录跳转到相关代码部分。

术语

  • Authentication:身份验证,验证主题身份的过程 - 用来验证某个人确实是他所说的人,例如 张三说他是张三  我们需要查看他的身份证查验这个人确实是张三,而不是随便一个人说他自己是张三就是张三。
  • Authorization:授权/访问控制,访问控制,用来验证某个主体或者人是否拥有某种权限,例如张三说天安门是他的不动产,我们需要验证天安门是否是他的不动产,如果张三有天安门的产权证那么天安门就是他的,这个过程就是访问控制。身份验证和访问控制的单词很像很容易混淆,其实根据举例应该可以区分这两个的区别, 身份验证用来验证某个主体是否是合法的,访问控制用来验证合法的主题是否拥有某种权限。
  • Cipher:密码 用来执行加解密的算法
  • Credential:凭据,用来验证主体身份的一条信息,身份验证期间,将一条或多条凭据和主体一起提交,与身份验证例子中的身份证一样。
  • Cryptography:加密包 消息摘要,加密的一个过程和cipher 是分开的,暂时不知道区别
  • Hash:哈希 使用Hash算法进行消息摘要
  • Permission:权限 用于声明某个方法或者行为需要什么权限,安全策略中的最低级别,当需要验证权限时,就已经完成身份验证,角色验证了。实际就是一个字符串。例如在
  • Principal:主要的标识 可以理解为用户的标识,类似于键值对的Key,根据key就可以从其他地方获取Value 避免主体过大
  • Realm:数据源 数据访问对象,通过Realm对象获取用户的信息,底层用户可能会存储Ldap或Mariadb数据库中,通过Realm接口可以屏蔽底层数据库的差异,让获取用户的信息具有统一性
  • Role:角色 相比于权限,可以把角色理解为一个权限的集合,例如张三需要打扫一座大楼的卫生,大楼的每层需要开门权限才可以开门进去打扫卫生,如果每层都给张三进行授权,权限太多且不好管理,我们使用角色进行分配的话,给张三一个保洁的角色就可以开启大楼的每层大门
  • Session:会话  一段时间内与软件系统交互的单个用户/主题相关联有状态的数据上下文,举个例子我们去游乐园需要购买门票,游乐园有的项目只允许进去一次,所以游玩这些项目的时候门票会被扣个洞,用于记录我们已经体验过这个项目了,我们出游乐园的时候会回收门票,这个门票这就可以理解为数据上下文信息。
  • Subject:主体 访问项目的不单单是指用户,也有可能是一段程序,主体用来表示这个意思

介绍


        Apache Shiro 是一个强大而灵活的开源安全框架,可以干净地处理身份验证、授权、企业会话管理和加密。 首要目标是易于使用和理解。安全性有时可能非常复杂,甚至很痛苦,但并非必须如此。框架应尽可能掩盖复杂性,并公开一个干净直观的API,以简化开发人员确保其应用程序安全的工作。

使用Shiro可以做什么?

  • 对用户进行身份验证以验证其身份
  • 对用户执行访问控制,例如:
    • 确定是否为用户分配了特定安全角色
    • 确定是否允许用户执行某些操作
  • 在任何环境中使用会话 API,即使没有 Web 或 EJB 容器也是如此。
  • 在身份验证、访问控制或会话生存期内对事件做出反应。
  • 聚合 1 个或多个用户安全数据源,并将其全部呈现为单个复合用户“视图”。
  • 启用单点登录 (SSO) 功能
  • 启用“记住我”服务以进行用户关联,而无需登录
  • ...等等 - 全部集成到一个有凝聚力的易于使用的 API 中。

功能


                                                                                                                         

主要的

  • Authentication 用户的认证
  • Authorization  访问控制
  • Session Management 会话管理,即使在非Web 或EJB应用程序也是可以使用
  • Cryptography 丰富的加密技术

附加的

  • Web Support  Shiro的Web支持Api 帮助轻松的保护Web应用程序
  • Caching Shiro的一级缓存,确保安全操作的同时保证快速高效
  • Concurrency 支持多线程应用程序的并发功能
  • Testing 支持单元和继承测试
  • Run As 允许用户可以使用其他用户的身份的功能
  • Remember  记住我,跨会话记住用户的身份

架构


        从架构上可以分为三个概念,subject、securityManager、reaim。Subject 可以理解为用户,实际不止代表用户也有可能是第三方服务、一个程序、定时任务等等,与软件进行交互的"实体"都可以称为Subject。securityManager 架构的核心,通过配置各种对象来完成业务逻辑,可以理解为钢铁侠的反应炉。Realm Shiro与数据源之间的桥梁,用于获取用户的数据。

       为了提供灵活的配置和可插拔性,在设计上采用高度模块化,甚至SecurityManager实现也没有做什么具体的事情,反而是将所有行为委托给各个嵌套/包装的组件进行。

简单的架构图

                                       

详细的架构图

                

  • Subject 软件交互的实体(用户、第三方服务、内外部程序)
  • Security Manager Shrio的架构核心,负责协调调度其他组件
  • authenticator 负责执行和响应用户身份的组件,当用户尝试登录时,由该逻辑执行
    • authenticationStrategy 身份验证策略,如果配置多个(Realm)数据源,用于管理Realm的策略,例如一个realm中的用户数据与当前需要验证的用户数据对应上了就代表对应成功,还是所有的Realm都对应上才算成功
  • Authorizer 负责确定用户在应用程序中访问控制的组件,用于验证用户的角色和权限
  • SessionManager 创建和管理用户的生命周期,可以在没有Web/Servlet 或EJB容器的情况下,管理本地用户会话
    • SessionDAO 持久性的管理用户的会话
  • CacheManager 创建和管理其他Shiro组件使用的实例生命周期,因为Shiro可以访问许多后端数据源进行身份验证、授权和会话管理。
  • Cryptography 加密包,对其他加密方式进行封装,让开发者更为简单使用加密功能
  • Realms 充当Shiro和数据源之间的桥梁,用于获取底层实际用户数据。

认证


                                 

        验证身份的过程,让用户证明自己的身份。需要提供主体凭据提交给Shiro来完成验证。

主体:标识属性,例如用户的姓名、用户名、身份证号码或定义的某种的Id这种唯一值。

凭据:一般指密码或指纹、视网膜,证书等。只有用户自己知道的秘密

主体/凭据 一般使用的都是 用户名/密码

  • 验证主体 - 身份验证可以分为三个步骤
    • 收集使用者提交的主体和凭据
    • 对主体和凭据进行校验
    • 通过则放行,否则重新让使用者提交或阻止访问

认证的步骤


                                        

  • Step1 接收到使用者提交的主体/凭据并产生一个实例对象,将主体/凭据转为Token对象
  • Step2 实例对象调用核心程序(Security Manager)方法将Token对象交给SecurityManager
  • Step3 身份验证组件接收Token对象,将Token对象委托给内部的身份验证器实例
  • Step4 如果配置了多个数据源,身份验证实例使用配置的身份验证策略启动多身份验证,在身份验证前中后期间,可以调用多个Realm获取库中的数据并给出验证结果。
  • Step5 检查每个Realm是否已经调用到。

访问控制


                                        

        管理资源访问的过程,控制谁有权限可以访问

授权三要素

        权限、角色、用户

权限

        权限代表安全策略的原子性的元素,基本是行为限制,可以明确标识执行的操作,一般用来Controller层的方法。设置权限的基本思想至少基于资源操作

        权限可以理解就是为一个普通的字符串,在校验用户权限时进行对比字符串。

权限的形式https://shiro.apache.org/permissions.html

角色

        角色是一个命名实体,代表一组权限的集合。角色分配给用户进行关联。

  • 隐式角色 不容易看到更加详细的信息
  • 显示角色 更容易看到详细的访问限制信息

新的 RBAC:基于资源的访问控制https://stormpath.com/blog/new-rbac-resource-based-access-control

用户

        使用者对应数据源中的用户,数据模型可以准确的定义如果允许A执行或者不执行某种操作

访问控制的方式

        访问控制支持三种方式进行检查用户的权限。

基于编程的方式

角色检查

public void role(){
    Subject subject = SecurityUtils.getSubject();

    if (!subject.hasRole("admin")) {
        throw new RuntimeException("此资源需要admin角色才能访问");
    }
}
Boolean hasRole(String roleName)当前用户是否分配了指定的角色,分配了返回true,否则返回false
Boolean[] hasRoles(List<String> roleNames)返回是否分配的Boolean结果数组
Boolean hasAllRoles(Collection<String> roleNames)是否分配了所有角色

角色断言

public void role(){
    Subject subject = SecurityUtils.getSubject();
    subject.checkRole("admin");
}
checkRole(String roleName)如果没有分配角色则抛出AuthorizationException异常
checkRoles(Collection<String> roleNames)如果没有分配给定的角色集合中的任意一个则抛出异常,异常同上
checkRoles(String…​ roleIdentifiers)同上方法,支持可变类型的方式

权限检查

public void per(){
    Permission printPermission = new PrinterPermission("laserjet4400n", "print");

    Subject currentUser = SecurityUtils.getSubject();

    if (currentUser.isPermitted(printPermission)) {
        //show the Print button
    } else {
        //don't show the button?  Grey it out?
    }


    Subject currentUser = SecurityUtils.getSubject();

    if (currentUser.isPermitted("printer:print:laserjet4400n")) {
        //show the Print button
    } else {
        //don't show the button?  Grey it out?
    }

    Subject currentUser = SecurityUtils.getSubject();

    Permission p = new WildcardPermission("printer:print:laserjet4400n");

    if (currentUser.isPermitted(p) {
        //show the Print button
    } else {
        //don't show the button?  Grey it out?
    }
}

Boolean isPermitted(Permission p)

isPermitted(String perm)

有该类权限返回true 反之 false

Boolean[] isPermitted(List<Permission> perms)

Boolean[] isPermitted(List perms)

返回一个Boolean结果数组

Boolean isPermittedAll(Collection<Permission> perms)

Boolean isPermittedAll(String…​ perms)

拥有全部权限返回true 反之 false

权限的断言

checkPermission(Permission p)没有包含的权限则抛出AuthorizationException异常
checkPermission(String perm)同上
checkPermissions(Collection<Permission> perms)必须包含所有指定的权限,否则抛出异常
checkPermissions(String…​ perms)同上

基于注解的方式

AspectJ示例应用程序https://github.com/apache/shiro/tree/main/samples/aspectj

Spring 集成https://shiro.apache.org/spring-framework.html

Guice 集成https://shiro.apache.org/guice.html

前端标签库

 JSP/GSP 标签库https://shiro.apache.org/web.html#tag_library

访问控制的执行顺序

  • Step1 Subject调用.hasRole方法(其他方法..)或者通过注解的方式需要进行访问控制
  • Step2 交由SecurityManager进行处理
  • Step3 SecurityManager不做具体处理,委托给Authorizer组件进行具体判定,默认由ModularRealmAuthorizer 实例处理。
  • Step4 检查每个配置Realm,从数据源获取用户数据后对比权限
    • 如果Realm实现了Authorizer接口则会执行对应重写的方法,如果没有实现该接口则不执行。
    • 如果重写的方法抛出异常,异常会作为AuthorizationException传播给调用者。如果是false则将不执行其他Realm中重写的方法。默认情况下一个Realm允许后将代表主体是允许的。

配置全局权限解析器 

        默认情况下会将字符串的权限转化为实例对象进行处理,例如源码默认使用WildcardPermissionResolver,且需要实现 PermisionResolverAware  xx

配置全局角色权限解析器 

        实现RolePermissionResolver 、全局则加上实现 RolePermisionResolverAware 这个接口。

领域 Realm

        可以访问特定应用程序的安全数据(用户、角色和权限)的组件。将应用程序的安全数据转化为Shiro理解的格式。

Realm 通常和数据源(数据库、LDAP、文件系统等)具有一对一的关系。所以接口的实现特定于数据库API来获取授权数据(JDBC、Hibernate,JPA)或者其他能获取数据库的API

                                        Realm 本质是一个特定于安全的DAO

        因为大多数数据都在数据库或存储服务器中,所以每个Realm都可以执行身份验证和授权操作。

领域认证

执行顺序

  • Step1 检查识别用户信息,例如Id等
  • Step2 根据识别的用户信息从数据源中获取用户信息(权限、角色等)
  • Step3 检查用户提交的凭证和数据源的凭证是否匹配,不匹配则抛出异常处理
  • Step4 返回一个AuthenticationInfo 实例,该实例为Shiro理解的格式封装用户数据

        以上操作仅仅是最基本的工作流。实际可以在这个过程中添加你想做的步骤,例如添加日志信息等。

构造Realm 

        直接实现Realm接口非常耗时且容易出错,一般推荐继承AuthorizingRealm 进行,而不是从头开始。

凭据匹配

        上面的流程中,Realm必须验证数据库中的凭证和用户提交的凭证是否一致。匹配则成功,反之失败。

        凭据匹配过程在所有应用程序中几乎相同,一般只是比较的数据有差异。为了确保这个过程是可以插入的,可以再必要的时候进行定制。通过AuthenticatingRealm 及其子类支持 CredentialsMatcher (提供了一些常用算法的比较例如 MD5、Hash等)的概念来执行凭证比较。

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

领域访问控制

基于角色的访问控制

  • Step1 Subject 委托给SecurityManager 用于确认是否分配了给定的角色
  • Step2 SecutiryManager 委托给Authorizer
  • Step3 然后Authorizer逐个引用所有授权领域,直到找到分配给主题给定的角色,如果都没有找到则拒绝访问
  • Step4 领域访问控制,根据getRoles()方法获取分配给主体的所有角色
  • 如果 AuthorizationInfo.getRoles 调用返回的角色列表中找到给定的角色,则授予访问权限

基于权限的访问控制

  • 在当主体调用重载方法isPermitted() / checkPermission()时
  • Step1 Subject委托SecurityManager
  • Step2 SecurityManager委托给Authorizer
  • Step3 然后Authorizer逐个引用所有授权领域,直到找到分配给主题给定的角色,如果都没有找到则拒绝访问
  • Step4 Realm 会执行以下操作检查是否允许主体
    • Step4.1 首先通过调用  AuthorizationInfo 上的 getObjectPermissions()和 getStringPermissions 方法并聚合结果来直接识别分配给 Subject 的所有权限。
    • Step4.2 如果配置了全局角色权限解析器,将会通过调用RolePermissionResolver.resolvePermissionsInRole()
    • Step4.3 对于来自a和b的聚合权限,则调用 implies()方法来检查这些权限中的任何一个是否隐含了已检查的权限,具体查看通配符权限。

通配符权限https://shiro.apache.org/permissions.html#wildcard_permissions

会话管理


        Shiro提供了完整的会话管理,从简单的命令行和企业集群Web应用程序。这个是Shiro单独的会话支持而不是Web容器中部署的应用程序或者EJB有状态的会话Bean。可以提供以下功能。

  • 基于POJO/J2SE  Shiro所有内容都是基于接口,并且通过POJO实现。这就意味着可以使用JavaBeans兼容的配置格式(JSON、YAML、springXML等)配置所有会话组件。轻松扩展Shiro的组件或者根据需求编写自己的组件,来完成会话管理。
  • 自定义会话存储 由于会话对象是基于POJO的,所以会话数据可以存储到任意数量的数据源中,意味可以准确自定义会话数据的位置,例如数据库、NOsql、文件系统、内存等
  • 独立于容器的集群 支持使用任何现成的网络缓存产品集群,例如Ehcache + Terracotta,Coherence 等,意味着配置一次会话集群,无论部署到哪台容器,会话都会以相同的方式进行集群化
  • 异构客户端访问 Shiro会话可以在各种客户端之间"共享"。
  • 事件侦听器 可以在会话的生命周期侦听事件,例如会话过期时做某种操作
  • 主机地址保留 会话会保留用户的主机IP或者主机名
  • 不活动/不过期 延长用户会话的事件touch()
  • Web使用 支持Servlet2.5 规范。可以在Web应用程序中使用Shiro会话,不需要更改任何web代码
  • SSO 因为基于POJO,所有会话可以存储到任意数据源,这样就可以在应用程序之间"共享",称之为简单版SSO。

使用会话

Subject currentUser = SecurityUtils.getSubject();

Session session = currentUser.getSession();
session.setAttribute( "someKey", someValue);


Subject.getSession(boolean create)
如果有一个,则忽略Boolean参数返回当前会话
如果没有则 Boolean为true 则新建一个 Boolean为false 不创建会话

会话管理器

        管理应用程序中所有主体的会话,创建、删除、不活动和验证等,和Shiro中的其他核心架构组件一样,由SecurityManager配置。默认实现DefaultSessionManager

会话超时

        默认情况下30分钟的会话超时时间,可以通过globalSessionTimeout设置超时时间。

侦听器

        通过实现SessionListener 或者 SessionListenerAdapter 可以在发生重要会话事件时做某种操作。

会话存储

        每次创建或更新会话时,都可以将数据保存到存储位置,已便应用程序可以访问到,通过实现SessionDAO 后交由SessionManager即可。如果不准备实现自己Dao层接口,可以使用 EHCache SessionDao

会话管理|阿帕奇四郎 (apache.org)https://shiro.apache.org/session-management.html

Web的支持

        根据以上的学习,下面进入进阶学习,关于Web支持相关文档请查阅官方资料,本文重点主要以Spring集成相关。

Apache Shiro Web Support | Apache Shirohttps://shiro.apache.org/web.html

其他功能

缓存

        保障安全操作尽可能快,缓存作为一个概念是Shiro的基本组成部分,但是实施完整的缓存机制将超出一个安全框架的范围,所以Shiro只是提供了一个抽象的接口,实际由用户去定义生产级别的缓存机制。

        A 返回实例和Shiro组件根据需要使用这些实例来缓存。所有实现Shiro组件将自动实现接受已配置的,用于获取实例

        Shiro SecurityManager 实现和 所有 AuthenticatingRealm 和 AuthorizingRealm 实现都实现了 CacheManagerAware。如果将 设置为 ,它将依次将其设置为 实现 CacheManagerAware 的各种 Realm 。

//TODO 缓存  缓存了什么数据怎么获取  怎么存储

缓存|阿帕奇四郎 (apache.org)

主体

自定义主体 (程序运行时)https://shiro.apache.org/subject.html

Spring集成

将Apache Shiro集成到基于Spring的应用程序中|阿帕奇Shirohttps://shiro.apache.org/spring-framework.html

环境配置

Shiro依赖

        <!-- Shiro 依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.8.0</version>
        </dependency>

完整依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.my</groupId>
    <artifactId>MyShiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>MyShiro</name>
    <description>MyShiro</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Shiro 依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.8.0</version>
        </dependency>

        <!-- 使用Thymeleaf整合Shiro标签 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

源码解析

登录流程解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值