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

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值