创建认证测试类
创建TestAuthenticator
类,代码如下:
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.SecurityUtils 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("heling", "123");
try{
System.out.println("认证状态:" + subject.isAuthenticated());
subject.login(token); //用户认证
System.out.println("认证状态:" + subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:用户名不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}
}
}
shiro.ini
内容为:
[users]
heling=123
运行测试结果为:
认证状态:false
认证状态:true
以上过程是shiro中最简单的一个认证,其中的权限管理数据是通过IniRealm
的形式去读取配置文件shiro.ini
来加载数据,但是以后的权限管理数据肯定都是来源于数据库的,所以我们要把数据更改到数据库。
Shiro从Realm
获取安全数据 (如用户,角色,权限),就是说SecurityManager
要验证用户身份,那么它需要从Realm
获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm
得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm
看DataSource
;Realm
可以决定我们的权限管理数据是来源于ini配置文件还是数据库,也就是数据的调配是由Realm
来执行的,为了搞清楚Realm
的调配流程,我们简单看一下认证的源码。
认证源码分析
用户名认证
首先在subject.login(token);
处打上断点,然后Debug运行。
程序停在断点之后,我们进行login方法。
可以看到虽然是subject调用的login,但是真正去执行的是DelegatingSubject
中的login,方法刚开始调用了一个clear方法,然后由安全管理器进行认证,内部的this
就是DelegatingSubject
类,token就是我们传过来的由身份信息和凭证信息组成的令牌。然后再进入securityManager
的login
方法。
我们发现实际上真正去执行的是DefaultSecurityManager
中的login
方法,方法刚开始就执行一个authenticate
方法,传入token,然后继续进入这个方法内部。
继续调用AuthenticatingSecurityManager
类中的authenticate
方法,在这个方法中又调用了当前类中的authenticator
对象的authenticate
方法,继续进入这个方法。
发现调用了AbstractAuthenticator
中的authenticate
方法,在这个方法中由调用了doAuthenticate
方法,继续进入doAuthenticate
。
来到了ModularRealmAuthenticator
类中的doAuthenticate
方法,方法开始时有一个断言询问是否配置Realm,然后getRealms
方法拿到了所有的域,我们只有一个域,所以realms.size() == 1
,进入doSingleRealmAuthentication
方法。然后进入doSingleRealmAuthentication
方法查看。
doSingleRealmAuthentication
方法刚开始询问realm是否支持token,realm肯定支持,所以realm调用getAuthenticationInfo
方法。
可以发现它是从缓存中拿取认证信息,因为程序第一次运行是没有缓存的,所以这次肯定拿不到信息。
继续调用doGetAuthenticationInfo
方法。
这里可以看到doGetAuthenticationInfo
方法将token取出来,然后强转成UsernamePasswordToken,然后通过upToken.getUsername()
获得用户名,最后使用getUser
方法拿到用户。我们继续进入getUsername
方法。
可以看到已经拿到用户名heling
。然后下一步回去。
再进入getUser
方法。
username被传进来了,往下走,发现this.users
的size=1,因为我们的配置文件shiro.ini
中只有一个用户。然后这里根据用户名拿到用户,返回SimpleAccount
,继续下一步。
这时已经拿到account,所以account肯定不为空,而且account也没有上锁,也没有做过验证密码是否过期的处理,所以最终在SimpleAccountRealm
类中通过doGetAuthenticationInfo
方法完成了一个用户名的认证。
密码认证
在return acount
之后继续下一步,回到了AuthenticatingRealm
类,
这里拿到的info肯定不为空了,继续下一步,因为token != null && info != null
,所以增加了缓存(this.cacheAuthenticationInfoIfPossible(token, info);
)。继续往下走。
如果info不为空的话,就进入一个断言,判断token中的密码和info中的密码是否相等。进入assertCredentialsMatch
方法查看。
进入方法,拿到了一个密码匹配器,如果密码匹配器不为空的话,就调用doCredentialsMatch
方法对token中的密码和info中的密码进行比对,如果密码不正确就抛出IncorrectCredentialsException
异常。所以最终密码的校验是在AuthenticatingRealm
类中的assertCredentialsMatch
方法中完成的。
如果以后我们要把数据换成数据库的实现,那么我们只需把SimpleAccountRealm
类中doGetAuthenticationInfo
方法的实现换成读取数据库,因为SimpleAccountRealm
继承自AuthorizingRealm
类,所以如果我们要自定义Realm,我们也应该自己写一个类去继承AuthorizingRealm
并重写doGetAuthenticationInfo
方法。密码校验是不需要我们自己处理的,因为我们在用户名认证之后返回info信息,AuthenticatingRealm
类中的assertCredentialsMatch
方法会自动帮我们校验。