本文主要是介绍【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 的学习教程(二)之认证、授权源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!