【Shiro】Shiro 的学习教程(二)之认证、授权源码分析

2024-09-08 11:52

本文主要是介绍【Shiro】Shiro 的学习教程(二)之认证、授权源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 1、背景
  • 2、相关类图
  • 3、解析
    • 3.1、加载、解析阶段
    • 3.2、认证阶段
    • 3.3、授权阶段

1、背景

继上节代码,通过 debug 进行 shiro 源码分析。

2、相关类图

debug 之前,先了解下一些类的结构图:

①:SecurityManager:安全管理器

在这里插入图片描述

  • DefaultSecurityManager
    • RememberMeManager:实现【记住我】功能
    • SubjectDAO:操作 Subject
    • SubjectFactory:Subject 工厂,用来生成 Subject
  • SessionsSecurityManager
    • SessionManager:用来管理 Session
  • AuthorizingSecurityManager
    • Authorizer:用来实现【授权】功能
  • AuthenticatingSecurityManager
    • Authenticator: 用来实现【认证】功能
  • RealmSecurityManager
    • Collection<Realm> realms:用来存储 Realm(由此可知:一个 SecurityManager 可以对应多个 Realm
  • CachingSecurityManager
    • CacheManager:用于实现【缓存】功能

②:Realm:数据域

在这里插入图片描述

  • IniRealm
    • resourcePath:ini 文件路径
    • Ini:将 ini 文件内容解析成 Ini 对象
  • TextConfigurationRealm
    • userDefinitions
    • roleDefinitions
  • SimpleAccountRealm
    • Map<String, SimpleAccount> users:存储 user
    • Map<String, SimpleRole> roles: 存储 role
  • AuthorizingRealm
    • boolean authorizationCachingEnabled: 是否进行授权缓存
    • Cache<Object, AuthorizationInfo> authorizationCache:授权缓存
    • PermissionResolver:权限解析器
    • RolePermissionResolver:角色权限解析器
  • AuthenticatingRealm
    • CredentialsMatcher:密码匹配器
    • boolean authenticationCachingEnabled:是否进行认证缓存
    • Cache<Object, AuthenticationInfo> authenticationCache:认证缓存
  • CachingRealm
    • CacheManager:实现缓存功能

③:Account:账号

在这里插入图片描述

  • SimpleAccount
    • SimpleAuthenticationInfo:认证信息
      • PrincipalCollection principals:凭证(用户名)
      • credentials:密码
      • ByteSource credentialsSalt:盐
    • SimpleAuthorizationInfo:授权信息
      • Set<String> roles:角色
      • Set<String> stringPermissions
      • Set<Permission> objectPermissions

3、解析

3.1、加载、解析阶段

  1. new IniRealm("classpath:shiro.ini")
    • IniRealm 调用类 Ini#load(Scanner) 加载并解析 shiro.ini 文件,解析结果存放属性 Map<String, Ini.Section> sections
    • 处理节点 users/roles:构造类 SimpleAccount(属性:认证:SimpleAuthenticationInfo authcInfo、授权:SimpleAuthorizationInfo authzInfo),并将处理结果存放在类 SimpleAccountRealm 的属性:Map<String, SimpleAccount> users、Map<String, SimpleRole> roles

IniRealm 构造器

在这里插入图片描述

①:Ini.fromResourcePath(resourcePath):通过类 Ini 进行解析、构造 Ini 对象

在这里插入图片描述

load(Scanner):最终调用这个方法进行解析

在这里插入图片描述

此方法的逻辑是:

  1. 先判断是否为注释(#; 符号开头):如果是,直接跳过;否则,进行解析。
  2. ini 文件格式:内容头、内容体。如果是内容头,则调用 addSection() 方法添加节点;否则,直接追加内容体

addSection() 方法:如果内容体不为空,则 Map<String, Ini.Section> sections 属性中添加新的节点

在这里插入图片描述

②:processDefinitions(Ini ini):解析 Map<String, Ini.Section> sections 属性中的节点,主要是 usersroles 节点

在这里插入图片描述

1、processRoleDefinitions() 方法:解析角色,构造 SimpleRole 对象,并添加到 SimpleAccountRealmMap<String, SimpleRole> roles

在这里插入图片描述

1-1、SimpleAccountRealm#add() 方法:将 SimpleRole 放入 roles

在这里插入图片描述

1-2 PermissionUtils.resolveDelimitedPermissions() 方法:通过 PermissionResolver 解析 permissions

在这里插入图片描述

【说明】在 ini 文件中,一个角色可以配置多个权限操作,通过 ","连接。如:admin=user:delete:1,order:query:*

1-2-1 resolvePermission():直接调用了 WildcardPermission 的构造器并返回

在这里插入图片描述

1-2-1-1 WildcardPermission#setParts() 方法:

在这里插入图片描述

2、processUserDefinitions() 方法:解析用户,构造 SimpleAccount 对象,并添加到 SimpleAccountRealmMap<String, SimpleAccount> users

在这里插入图片描述

由以上代码知:也可以只输入密码,不用添加角色

[users]
#用户名=密码
christy=123456

2-1、new SimpleAccount()SimpleAccount 构造器

在这里插入图片描述

2-1-1、new SimplePrincipalCollection():将当前 realm 名称与凭证(用户名)存入 Map<String, Set> realmPrincipals 属性中

在这里插入图片描述

2-1-1-1、add() 方法:

在这里插入图片描述

  1. setRealm(Realm realm):给 SecurityManager、认证器、授权器设置 realm

    • 给类 ModularRealmAuthenticator 设置 realms
    • 给类 ModularRealmAuthorizer 设置 realms

在这里插入图片描述

RealmSecurityManager#afterRealmsSet() 方法:被子类 AuthenticatingSecurityManagerAuthorizingSecurityManager 重写

在这里插入图片描述

AuthorizingSecurityManager#afterRealmsSet() 方法又去调用父类 AuthenticatingSecurityManager#afterRealmsSet() 方法:

  1. SecurityUtils.getSubject():通过类 ThreadContext 的属性 ThreadLocal<Map<Object, Object>> resources 获取与当前线程绑定的 Subject(Subject 来源于 DefaultSecurityManager.createSubject() 方法:最终通过 DefaultSubjectFactory 类进行 new DelegatingSubject())

1、getSubject() 方法:通过 ThreadContext 获取,如果获取成功,则直接返回;否则,先创建 Subejct,再绑定,最后返回

在这里插入图片描述
1-1、ThreadContext#getSubject() 方法:最终通过属性 ThreadLocal<Map<Object, Object>> resources 中获取,key 为 ThreadContext.class.getName() + "_SUBJECT_KEY"

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

ThreadLocal<Map<Object, Object>> resources:是与当前线程绑定的

1-2、new Builder()Builder 是接口 Subject 的一个内部类

在这里插入图片描述

1-2-1、newSubjectContextInstance() 方法:创建 DefaultSubjectContext 对象

在这里插入图片描述

1-3、setSecurityManager() 方法:设置 SecurityManager

在这里插入图片描述

1-3-1、put() 方法:最终往属性 Map<String, Object> backingMap 中 put

在这里插入图片描述

DefaultSubjectContext:是一个 Map 结构

在这里插入图片描述

1-4、buildSubject() 方法:创建 Subject。通过 SecurityManager 创建

在这里插入图片描述

1-4-1、createSubject() 方法:

  • 复制了一份 SubjectContext
  • 验证 SubjectContext 是否有 SecurityManager
  • 解析 Session。从属性 backingMap 获取。默认为 null
  • 解析 凭证
  • 创建 Subject。通过工厂创建 Subject
  • 保存 Subject 的凭证、session

在这里插入图片描述

1-4-1-1、resolveSession() 方法

  • 先从 SubjectContext 中获取 Session。默认为 null
  • 再从 Session 中获取

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

1-4-1-2、resolvePrincipals() 方法:

  • 先从 SubjectContext 中获取 凭证。默认为 null
  • 再从 RememberMeManager 获取
    在这里插入图片描述

1-4-1-3、createSubject() 方法:直接 new 了一个 DelegatingSubject

在这里插入图片描述

1-4-1-4、save() 方法:通过 subjectDAO 去操作

在这里插入图片描述

save() 方法:

在这里插入图片描述

3.2、认证阶段

  1. subject.login(token)
    • Subject 先委托给类 DefaultSecurityManager 进行认证
    • 再由类 AuthenticatingSecurityManager 委托给类 AbstractAuthenticator 进行认证(实际是它的子类 ModularRealmAuthenticator 进行认证)
    • 最终通过类 SimpleAccountRealm#doGetAuthenticationInfo() 进行用户名认证;通过类 CredentialsMatcher 进行密码认证

DelegatingSubject#login():委托给 SecurityManager 去认证

在这里插入图片描述

1、DefaultSecurityManager#login()

  • 通过调用父类 AuthenticatingSecurityManager#authenticate() 方法 去实现认证
  • 认证成功后,去创建 subject
  • 【记住我】逻辑处理

在这里插入图片描述

1-1、AuthenticatingSecurityManager#authenticate() 方法:通过 Authenticator 去认证

在这里插入图片描述

1-1-1、AbstractAuthenticator#authenticate() 方法:进行认证

  • 如果认证成功,如果有监听器 AuthenticationListener,则执行成功后的动作,并返回 AuthenticationInfo 信息
  • 如果认证失败(info == null),则抛异常

在这里插入图片描述

【注意】:如果返回的 info 结果为空,则 throw new AuthenticationException(msg);

1-1-1-1、ModularRealmAuthenticator#doAuthenticate() 方法:通过其子类去认证

  • 如果只配置一个 realm,则调用 doSingleRealmAuthentication() 方法;否则,调用 doMultiRealmAuthentication() 方法

在这里插入图片描述

1-1-1-1-1、doSingleRealmAuthentication() 方法:

  • 先判断 realm 是否支持当前的 token(类型:UsernamePasswordToken?还是自定义类型?)
  • 根据 token 从 realm 中获取 info,如果 info == null,则抛异常

在这里插入图片描述

1-1-1-1-1-1、supports() 方法:判断当前的 realm 是否支持 token

这里:判断 token 类型是否为 AuthenticationToken 类型(默认为 UsernamePasswordToken

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

token 不为空,且 AuthenticatingRealm 有一个属性 authenticationTokenClass,它的类型要和 token 一直才返回 true;否则,返回 false

在这里插入图片描述

在初始化过程中,authenticationTokenClass 已经被赋值为了 UsernamePasswordToken 类型。所以,如果 token 类型为 UsernamePasswordToken,返回 true;否则,返回 false

1-1-1-1-1-2、getAuthenticationInfo() 方法:获取认证信息

  • 先从缓存获取
  • 如果缓存有,则通过 CredentialsMatcher 直接进行密码匹配,如果匹配成功,则直接返回 info;否则,则抛出异常
  • 如果缓存没有,则从 realm 中拿(默认是从 SimpleAccountRealm 中拿取,ini 文件中配置的账号、角色存储在这个里面),再放入缓存中,进行密码匹配

在这里插入图片描述

1-1-1-1-1-2-1、getCachedAuthenticationInfo() 方法:从缓存中获取

在这里插入图片描述

1-1-1-1-1-2-1-1 getAvailableAuthenticationCache() 方法:拿取可用的 cache

  • 先获取 cache,默认为 null
  • 判断是否需要缓存,如果需要,且没有 cache,则从 CacheManager 中拿

在这里插入图片描述

1-1-1-1-1-2-1-1-1 getAuthenticationCacheLazy() 方法:从 CacheManager 中拿去 cache

在这里插入图片描述

1-1-1-1-1-2-2、doGetAuthenticationInfo() 方法:从 realm 中获取认证信息

  • doGetAuthenticationInfo() 是个抽象方法,留给子类实现。
  • 这里默认的实现是 SimpleAccountRealm,通过用户名获取 SimpleAccount

在这里插入图片描述

1-1-1-1-1-2-3、cacheAuthenticationInfoIfPossible() 方法:如果允许进行缓存,则缓存信息

在这里插入图片描述

1-1-1-1-1-2-4、assertCredentialsMatch() 方法:通过 CredentialsMatcher 进行密码认证

在这里插入图片描述

1-2、createSubject() 方法:创建 Subject

最终流程:

  1. 创建 SecurityManager:SecurityManager 是用来提供安全服务的,所以在做 Shiro 认证的时候要先创建此对象
  2. 主体 Subject 提交请求给 SecurityManager
  3. SecurityManager 调用 Authenticator 组件做认证
  4. Authenticator 通过 Realm 来从数据源中获取认证数据

3.3、授权阶段

  1. subject.hasRole("admin"):判断是否有角色

DelegatingSubject#hasRole() 调用 SecurityManager#hasRole() 进行认证:

在这里插入图片描述

AuthorizingSecurityManager 又调用 ModularRealmAuthorizer#hasRole()

在这里插入图片描述

ModularRealmAuthorizer 又调用 AuthorizingRealm#hasRole()

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

AuthorizingRealm#doGetAuthorizationInfo(PrincipalCollection) 是个抽象方法,由子类实现:

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


protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
}

这篇关于【Shiro】Shiro 的学习教程(二)之认证、授权源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1148043

相关文章

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

Java JDK1.8 安装和环境配置教程详解

《JavaJDK1.8安装和环境配置教程详解》文章简要介绍了JDK1.8的安装流程,包括官网下载对应系统版本、安装时选择非系统盘路径、配置JAVA_HOME、CLASSPATH和Path环境变量,... 目录1.下载JDK2.安装JDK3.配置环境变量4.检验JDK官网下载地址:Java Downloads

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

使用Docker构建Python Flask程序的详细教程

《使用Docker构建PythonFlask程序的详细教程》在当今的软件开发领域,容器化技术正变得越来越流行,而Docker无疑是其中的佼佼者,本文我们就来聊聊如何使用Docker构建一个简单的Py... 目录引言一、准备工作二、创建 Flask 应用程序三、创建 dockerfile四、构建 Docker

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重