摘要:本文通过源码追踪,介绍 Shiro 如何实现简单的登陆控制。本文的源码追踪十分详细,基本所有的调用以及接口对象关系都包含其中。
首先我们看一张图,这张图简要介绍了Shiro
登陆说需要的主要组件,大家可以查看官方的登陆步骤参考。
我们接下来更加详细介绍一下在登陆过程中源码的调用流程,前6步为生产环境的代码,也就是我们自己需要实现的最简单的登陆逻辑代码。从第7步开始为 Shiro
源码的调用。
1. SecurityManager
的工厂类
创建一个 SecurityManager
的工厂类,配置权限,这里我们可以使用最简单的 ini 配置文件来导入账号和权限。
1 | Factory<org.apache.shiro.mgt.SecurityManager> factory = |
shiro-realm.ini
文件
1 | [main] |
我们在下面的步骤中我们会详细介绍自定义的 myRealm1
。
2. SecurityManager
实例
使用工厂模式获取一个 SecurityManager
的实例。
1 | org.apache.shiro.mgt.SecurityManager securityManager |
3. 配置当前 SecurityManager
使用 SecurityUtils
设置该实例为当前的 SecurityManager
。
1 | SecurityUtils.setSecurityManager(securityManager); |
4. 获取 Subject
使用 SecurityUtils 获取当前的 Subject
1 | Subject subject = SecurityUtils.getSubject(); |
5. 准备登陆的数据,
简单的登陆数据为:用户名和密码。
1 | UsernamePasswordToken token = new UsernamePasswordToken("wang", "123"); |
这组数据应该是放到 myRealm1
中进行匹配的。所以后面我们会看到这个用户名和密码在 myRealm1
是如何进行验证的。
6. 开始登陆
使用 Subject
的 login
方法传入准备的登陆数据来进行验证。
1 | try { |
7. Subject
的 login
方法
login
为 Subject
的接口,实际调用的是 DelegationgSubject
的 login
的实现。
1 | public void login(AuthenticationToken token) throws AuthenticationException |
8. SecurityManager
的 login
DelegationSubject
只是一个代理,在 login
方法中调用 securityManager.login(this, token)
, this
指的是 Subject
对象。
1 | Subject subject = securityManager.login(this, token); |
9. DefaultSecurityManager
的 login
这个 login
还是一个接口,这回是 SecurityManager
的接口,实际调用 DefaultSecurityManager
的 login
实现。
1 | public Subject login(Subject subject, AuthenticationToken token) |
10. login
的具体实现
在该login 实现中,定义了一个 AuthenticationInfo
的 info
对象,这个 info 变量将是我们授权过程重要对象。然后调用父类的 authenticate(token)
方法,该方法为 AuthenticatingSecurityManager
抽象类中的方法,该方法实际是调用了成员变量 private Authenticator authenticator
的 authenticate(token)
方法。该方法是 Authenticator
的接口,这时候调用抽象类 AbstractAuthenticator
的实现。这个 authencitcator
在 AuthenticatingSecurityManager
的构造函数中实际上是实例了 ModularRealmAuthenticator
1 | AuthenticationInfo info; |
11. doAuthenticate
开始授权
上一步的 authenticate
方法实现中,调用了 doAuthenticate(token)
来开始授权。这个 doAuthenticate
实际上是调用的 ModularRealmAuthenticator
的 doAuthenticate
。该方法首先获取 realm
的数量,如果是一个 realm
则进入 doSingleRealmAuthentication
,否则进入 doMultiRealmAuthentication
。
1 | protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { |
12. Realm
简单案例是单 realm
,所以进入 doSingleRealmAuthentication
,函数中调用 AuthenticationInfo info = realm.getAuthenticationInfo(token);
来获取 info
。这个 realm
实际上是我们在配置 ini 文件中的 MyRealm1
类。
1 | public class MyRealm1 implements Realm { |
13. Realm
授权逻辑
如果不自己实现 Realm
将会调用了抽象 realm
实现 AuthenticatingRealm
的 getAuthenticationInfo
方法。而这里将会调用我们自定义的 Realm
的 getAuthenticationInfo
,自定义getAuthenticationInfo
十分简单就是匹配用户名和密码成功就返回SimpleAuthenticationInfo
。
这里我们介绍一下抽象的 AuthenticatingRealm
的实现方法,该方法分两步,第一步是拿到 info,第二步是验证 credential 信息。
1 | public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { |
info = doGetAuthenticationInfo(token);
这个方法直接把传入的token
强制转化为UsernamePasswordToken
父类。然后使用getUser(token)
获取账号信息SimpleAccount account = getUser(upToken.getUsername());
实际上这个User
是在配置 realm 的时候能够获取的,可以是最简单的 user/password 配置。assertCredentialsMatch(token, info);
这个方法是使用token
来匹配info
信息,如果没有异常表示匹配成功,如果有异常则表示匹配失败。 该方法是匹配逻辑的关键,首先获取一个matcher
:cm,本例最简单的SimpleCredentialsMatcher
。 然后cm.doCredentialsMatch(token, info)
来进行匹配,该函数就是获取token
中的credential
,然后获取AuthenticationInfo info
的credential
然后判断是否相等,具体如何相等,这里就是简单的匹配。
如果匹配成功无异常,返回知道返回 info 为止。
1 | public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { |
至此一个完整的简单的 Shiro 登陆的源码的详细流程就走完了。