认证源码流程分析

1、在进行认证的时候,我们将用户的信息封装好一个对象 UsernamePasswordToken 之后,是通过 Subject.login(tpken) 方法来进行提交认证的,这也是第一步,用 Debug 模式启动一次认证操作
image.png
2、下一步进入到一个 DelegatingSubject 的类中,执行依旧是 login() 方法。这个就是 subject.log() 的具体实现方法。同时我们看到一个之前要了解的一个异常 AuthenticationException 认证失败的父类异常。
红色框中的代码是: Subject subject = this.securityManager.login(this, token);
咱们之前有提到过, subject 的认证是托管给 SecurityManager 来进行操作的,而这里做的就是托管给 SecurityManager
image.png
securityManager.login(this, token) 方法上需要两个参数

  • this:当前的主体,当前认证的主体。
  • token:我们的认证信息。其中密码是以数组的形式呈现的。

image.png
image.png

这里使用的是 securityManager ,是SecurityManagerFactory创建的并 绑定到当前环境中。
image.png
3、继续进入,这一步就执行了一个内部的方法 authenticate(token)
image.png
点击去发现执行的只有一句代码。其中的 authenticator 就是认证器,这也就是说SecurityManager 也没有进行登录的认证,而是继续的进行委托,将这件事情委托给 Authenticator 认证器再继续进行认证操作。既然是委托,所以 token 则继续的进行传递。
image.png
4、继续下一步,我们就来到了 AbstractAuthenticator.authenticate(AuthenticationToken token) 方法中了。会先做一次非空的判断,接着就执行到了 info = doAuthenticate(token);
这里执行的是内部的一个认证的方法,token 继续传递。其中我们看到最终想要的是一个 AuthenticationInfo 类型的对象,
image.png
我们继续点进去之后,很多同学发现了,不是说内部的方法吗?为什么跳转到其他的类中,执行方法。这里我们首先看一下 AbstractAuthenticator 是一个抽象类,在本类中的 doAuthenticate(AuthenticationToken var1) 这个方法是一个抽象方法,而跳转到的类是 ModularRealmAuthenticator ,他继承了 AbstractAuthenticator ,同时重写了 doAuthenticate 方法,所以这里会跳转到其子类重写的方法中。
image.png
我们继续回到代码中来,这里做先获取我们的 Realms ,这里的 Realms 就是从我们的配置中获取的
image.png
点进去继续查看代码,发现调用的是 realm.getAuthenticationInfo(token); 方法,返回的就是我们之前提到的 AuthenticationInfo 对象。
AuthenticationInfo 在这里表示的就是,通过用户名在 Realm 中查询到用户信息,然后将查询的到信息封装为 AuthenticationInfo 进行返回。
接下来开始先认证用户名,接着再认证密码。所以这里,如果判断返回的 info 为空(因为是先更具用户名进行判断),所以就会抛出 UnknownAccountException 的异常。
image.png
这里我们就要先做”用户名”的认证 。
继续跟进代码,我们跟到了 AuthenticatingRealm.getAuthenticationInfo(AuthenticationToken token) 方法中了。先会去缓存中获取AuthenticationInfo,代码info = doGetAuthenticationInfo(token)这个方式就是执行主要认证的核心点了,去执行自定义的 Realm 的重写认证的方法doGetAuthenticationInfo 。token 继续进行传递
image.png
继续跟进,就到我们自定义的Realm.doGetAuthenticationInfo()
image.png
到这里认证”用户名”的操作结束了,接着我们要一层一层的进行返回。我们先回到了 AuthenticatingRealm.getAuthenticationInfo(AuthenticationToken token) 方法中。因为查询出来的 info 肯定是不为空的,我们直接看到 assertCredentialsMatch(token, info); 这个方法中。 接下来就是开始认证”密码”。
image.png
跟进代码,我们会跳转到
AuthenticatingRealm.assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
这个方法,第一句话是获取加密器
image.png
getCredentialsMatcher() 获取凭证(密码)匹配器,这里返回了HashedCredentialsMatcher就是我们在ShiroConfig.java中配置的
image.png
image.png
重点在 cm.doCredentialsMatch(token, info) 这个方法,他需要两个参数,都是数据类型

  • token:当前待认证密码的用户信息
  • info:从Realm 中根据用户名查询出来的用户信息

跟进代码后,这个方法的代码很简洁明了,就是从我们的两个参数中取出密码的值,然后进行判断是否相等,返回一个 boolean 的数据类型即可。
image.png
在两个密码判断完成之后,返回到上一层代码中 if (!cm.doCredentialsMatch(token, info)) ,如果密码不正确就会抛出 IncorrectCredentialsException 这个异常,我们之前也是提到过的。
image.png
最后,密码验证通过后返回AuthenticationInfo对象,这个info包含principals、credentials、credentialsSalt
image.png
之后就是一层层的返回AuthenticationInfo对象了
image.png
image.png
走到AbstractAuthenticator.authenticate(),doAuthenticate(token)认证成功获取到认证对象
image.png
image.png
创建Subject对象
image.png
image.png
执行DefaultSecurityManager.createSubject(context) 具体创建,这个过程使用了代理模式 过程复杂就不具体赘述了

总结:

  1. 页面传来认证信息,我们封装成 UsernamePasswordToken
  2. 使用 Subject.login() 方法提交认证,参数就是 token
  3. Subject 委托给 SecurityManager,然后执行 SecurityManager.login() 方法,这里需要借助 Realm 数据源进行认证。
  4. SecurityManager 再次委托给 Authenticator 进行真正的认证过程
    1. 先认证用户名
    2. 用户名正确后再认证密码

用一幅图来总结一下对上述代码的流程