权限管理 第2课 Shiro登录的源码解析
权限管理 第1课 Shiro最简demo
权限管理 第2课 Shiro登录的源码解析
我们上一课通过ini配置方式实现了最简的shiro登录的操作,可是具体是怎么实现操作的呢?我们在这一章通过debug方式看看shiro登录的实现原理
我们仍旧基于上一课的代码来作为例子。
1. 首先大体了解一下执行流程
我们在剖析源码之前先看一张图,这张图讲述了Shiro登录过程中执行的流程:
- 构造SecurityManager环境,就是上一课的通过SecurityManagerFactory创建SecurityManager对象,然后绑定到当前环境的操作;
- Subject.login(),就是上一课中将
token
作为参数的登录的部分,这里将是我们本次深度剖析的重点;
- 剩下的步骤我们没有在上一节课看到,我们这次来慢慢剖析。
2. 开始debug源码
1.
我们首先把断点点在subject.login();
登录的方法这儿,开始debug
执行,执行到断点卡住的时候,我们点击F7
进入看一下。
2.
首先我们看到这次我们跳转到了DelegatingSubject类,这是Subject的一个实现类。
1. 代码停在了this.clearRunAsIdentitiesInternal();
这个方法上,这个方法的作用是清除session中的已存在的登录信息,我们既然是在登录,那么就需要先将现在的登录状态信息清除掉;
2. 我们点击F8
执行到下一步,就是图中红色方框的位置。我们看到这里调用了this.securityManager
对象的login()
方法,就如上面流程图中的第3步,我们Subject.login()
方法其实是调用securityManager.login()
方法实现的登录操作;
3. OK,我们继续看一下securityManager.login()
的实现原理吧,按F7
进入securityManager.login()
3.
我们看到代码跳转到了DefaultSecurityManager
的类,是SecurityManager的实现类。我们先解析一下两个对象:
1. AuthenticationInfo
存储的是主体(Subject)的身份认证信息,说白了是realm
的身份信息。
2. AuthenticationToken
用于收集用户提交的身份(如用户名)及凭据(如密码),说白了是Subject
传入的身份信息。
3. 我们看到此时方法中调用了一个this.authenticate();
的身份认证方法,我们按F7
进入继续探究。
4.
到了这一步我们可以看到,此刻方法调用了authenticator
对象的authenticate()
认证方法。Authenticator正是Shiro的身份认证器。
也就是说一开始的Subject.login()
调用了SecurityManager.login()
方法,而SecurityManager.login()
调用了this.authenticate();
方法,而最终这个方法调用的是Authenticator
的认证方法,绕了这么一大圈,最后使用的还是Authenticator
身份认证器进行认证登录的。
但是之所以Subject
不能直接调用Authenticator
身份认证器是因为所有的主体的操作都应该由安全管理器SecurityManager
进行统一管理,只开放对Subject
的API接口,这样才能使程序更加安全。
还没完,我们继续探究Authenticator
身份认证器如何进行认证操作,我们按F7
进入下一级。
5.
第70行,首先判断token是否为空,为空的抛出异常IllegalArgumentException
非法参数异常。
第77行,调用了一个身份验证的方法,我们进去看看,按F8
下一步到这里,然后按F7
进入。
6.
嗯?这一步我们终于看到了Shiro三大要素之一的Realm
。
第107行,this.assertRealmsConfigured();
是读取资源配置,我们继续往下执行,然后看一下reamls的数据:
哦?好熟悉的数据,好像是我们在shiro.ini
配置的两条参数。说明这里已经把我们写在配置文件里的模拟数据库存储的用户的用户名信息读取了出来。
我们继续,到109行,这个方法将realms
列表和Subject
输入的AuthenticationToken
的对象传了进去进行对比,然后返回对比的结果,看看是否存在和Subject
传入的用户名相同的数据。这里比较绕,说白了就是去realms
列表里找Subject
输入的用户名是否存在,如果存在,返回存储的正确的用户密码。具体的我们F7
进去看看。
7. 用户不存在异常
首先判断传入的token是否支持realm,不支持抛异常UnsupportedTokenException
,Token不支持异常;
然后realm.getAuthenticationInfo(token)
是用token的用户名在realms里寻找相同项,寻找到的话,返回realms
列表里的那条数据,否则返回null
,这里在下一步我们会详细讲解。
再然后,开始判断,返回的info
数据,如果是null
的话,说明Subject
输入的用户名不存在,此处抛出异常UnknownAccountException
。
所以我们之前在前一课的测试类里捕获登录抛出的这个异常时被定义为"用户名不存在"的出处就在这里。真的隐藏的好深是不是~~~
那么下面继续执行,我上图是用户名不存在的执行到了这一步,下面我会切换成密码不对,再执行一次,接着演示,所以下面的讲解是基于用户名存在的情况下。
8. 密码不正确异常
此处我们假设用户名是对的,那么我们要在上一步的第59行进去,那么就得到了本步骤的图,如果此时用户名是存在的,那么第一个红框中返回的info是有数据的。那么就执行到了第二个红框,下图就是进入第二个红框的代码:
此处我们在第218行进入看一眼,就是下图代码,代码里就已经很明确了,分别从Subject
的token和Realm
的info提取凭证对象,然后进行判断,这里就是最终的凭证(一般是密码)之间的对比了,返回 true/false。
那么返回上面第二图的第218行,如果返回的是false,那么就抛出异常IncorrectCredentialsException
,不正确凭证异常。
我们之前在前一课的测试类里捕获登录抛出的这个异常时被定义为"密码错误"的出处就在这里。唉~ 心累~
9.
剩下的就顺理成章啦,如果账号密码都对的话,就是可以在realms
里找到用户名,并且密码也是匹配正确的,就直接登录成功啦,至于session
之类的东西,我们后面再说,本章就到这里。
如果本文有错误或对本文有不理解的地方欢迎评论 ^_^
如果本文有帮助到您,可以点一下右上角的赞哦,谢谢啦