
创建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方法会自动帮我们校验。