本文主要是介绍【Shiro】Shiro 的学习教程(二)之认证、授权源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 1、背景
- 2、相关类图
- 3、解析
- 3.1、加载、解析阶段
- 3.2、认证阶段
- 3.3、授权阶段
1、背景
继上节代码,通过 debug 进行 shiro 源码分析。
2、相关类图
debug 之前,先了解下一些类的结构图:
①:SecurityManager:安全管理器

DefaultSecurityManager:RememberMeManager:实现【记住我】功能SubjectDAO:操作 SubjectSubjectFactory: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:存储 userMap<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、加载、解析阶段
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):最终调用这个方法进行解析

此方法的逻辑是:
- 先判断是否为注释(
#、;符号开头):如果是,直接跳过;否则,进行解析。 - ini 文件格式:内容头、内容体。如果是内容头,则调用
addSection()方法添加节点;否则,直接追加内容体
addSection() 方法:如果内容体不为空,则 Map<String, Ini.Section> sections 属性中添加新的节点

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

1、processRoleDefinitions() 方法:解析角色,构造 SimpleRole 对象,并添加到 SimpleAccountRealm 的 Map<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 对象,并添加到 SimpleAccountRealm 的 Map<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() 方法:

-
setRealm(Realm realm):给SecurityManager、认证器、授权器设置 realm- 给类 ModularRealmAuthenticator 设置 realms
- 给类 ModularRealmAuthorizer 设置 realms

RealmSecurityManager#afterRealmsSet() 方法:被子类 AuthenticatingSecurityManager、AuthorizingSecurityManager 重写

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

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、认证阶段
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
最终流程:
- 创建 SecurityManager:SecurityManager 是用来提供安全服务的,所以在做 Shiro 认证的时候要先创建此对象
- 主体 Subject 提交请求给 SecurityManager
- SecurityManager 调用 Authenticator 组件做认证
- Authenticator 通过 Realm 来从数据源中获取认证数据
3.3、授权阶段
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 的学习教程(二)之认证、授权源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!