第二节 自定义Realm之继承AuthorizingRealm
一、Realm基本架构
为了快速上手,我们需要知道的是,Shiro将数据库中的数据,存放到Realm这种对象中。而Shiro提供的Realm体系较为复杂,一般我们为了使用Shiro的基本目的就是:认证、授权。
所以,一般在真实的项目中,我们不会直接实现Realm接口,也不会直接继承最底层的功能贼复杂的IniRealm。我们一般的情况就是直接继承AuthorizingRealm,能够继承到认证与授权功能。它需要强制重写两个方法:
public class DefaultRealm extends AuthorizingRealm {
//强制重新授权方法,以后再说
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principalCollection) {
return null;
}
//强制重写的认证方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
return null;
}
}
既然说Realm将数据库中的数据存放到realm中,那么数据库操作在哪里呢?当然就是在我们的doGetAuthenticationInfo方法中操作了。有JAVAWEB经验的朋友肯定知道,用户数据一般都会放到数据库中。所以,我们在doGetAuthenticationInfo方法内部是需要查询数据库操作的。下面是一个简单重写此方法的例子。
/**
* 强制重写的认证方法
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
//还记得吗,token封装了客户端的帐号密码,由Subject拉客并最终带到此处
String clientUsername = (String) token.getPrincipal();
//模拟一个Service
SecurityService securityService = new SecurityService();
//通过Service查询数据库,获取到正确的密码
String passwordFromDB = securityService.findPasswordByUsername(clientUsername);
if(passwordFromDB == null){
//如果根据用户输入的用户名,去数据库中没有查询到相关的密码
throw new UnknownAccountException();
}
/**
* 返回一个从数据库中查出来的的凭证。用户名为clientUsername,密码为passwordFromDB 。封装成当前返回值
* 接下来shiro框架做的事情就很简单了。
* 它会拿你的输入的token与当前返回的这个数据库凭证SimpleAuthenticationInfo对比一下
* 看看是不是一样,如果用户的帐号密码与数据库中查出来的数据一样,那么本次登录成功
* 否则就是你密码输入错误
*/
return new SimpleAuthenticationInfo(clientUsername, passwordFromDB , getName());
}
二、配置文件
在配置文件中,引入我们编写的realm。
myRealm = com.safesoft.DefaultRealm 表示 创建一个DefaultRealm实例,其引入名为myRealm。
在securityManager对象里面使用$引入此Realm类的实例。
[main]
myRealm=com.safesoft.DefaultRealm
securityManager.realms=$myRealm
[users]
jay=123456
下面编写一个测试用例,在subject.login(token);处打一个断点,可进行源码追踪。
public class AuthencateTest {
@Test
public void test() {
//读取配置文件,相当于在加载数据源
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
//SecurityManager 是Shiro内部的底层实现,几乎所有功能都由其实现
SecurityManager sm = factory.getInstance();
//SecurityUtils是一个工具,方便用户调用,它封装了SecurityManager
SecurityUtils.setSecurityManager(sm);
//生成一个SecurityManager的门面类,即Subject。
Subject subject = SecurityUtils.getSubject();
//封装用户的数据
UsernamePasswordToken token = new UsernamePasswordToken("jay", "123456");
//Subject接收到的方法参数,最终将会传到SecurityManager中进行验证
//将用户的数据token 最终传递到Realm中进行对比
subject.login(token);
//判断本帐号是否已经被认证
Assert.assertEquals(true, subject.isAuthenticated());
}
}
三、代码追踪
如果你已有相关的Shiro基础,那么相信你已经知道如何使用自定义Realm。如果你还不知道如何在项目中使用自定义Realm,今后的章节中有如何使用Realm的例子,所以不用担心。
现在,我先来通过源码分析一下认证的基本流程。
(1)通过Debugger模式追踪源码subject.login(token) 发现。首先是进入Subject接口的默认实现类。果然,Subject将用户的用户名密码委托给了securityManager去做。
(2)然后,securityManager说:“卧槽,认证器authenticator小弟,听说你的大学学的专业就是认证呀,那么这个认证的任务就交给你咯”。遂将用户的token委托给内部认证组件authenticator去做。
(3)事实上,securityManager的内部组件一个比一个懒。内部认证组件authenticator说:“你们传过来的token我需要拿去跟数据源Realm做对比,这样吧,这个光荣的任务就交给Realm你去做吧”。Realm对象:“MMP”。
(4)Realm在接到内部认证组件authenticator组件后很伤心,最后对电脑前的你说:“大兄弟,对不住了,你去实现一下呗”。从图中的方法体中可以看到,当前对象是Realm类对象,即将调用的方法是doGetAuthenticationInfo(token)。而这个方法,就是你即将要重写的方法。如果帐号密码通过了,那么返回一个认证成功的info凭证。如果认证失败,抛出一个异常就好了。你说:“什么?最终还是劳资来认证?”没错,就是苦逼的你去实现了,谁叫你是程序猿呢。所以,你不得不查询一下数据库,重写doGetAuthenticationInfo方法,查出来正确的帐号密码,返回一个正确的凭证info。
(5)好了,这个时候你自己编写了一个类,继承了AuthorizingRealm,并实现了上述doGetAuthenticationInfo方法。你在doGetAuthenticationInfo中编写了查询数据库的代码,并将数据库中存放的用户名与密码封装成了一个AuthenticationInfo对象返回。可以看到下图中,info这个对象是有值的,说明从数据库中查询出来了正确的帐号密码。
那么,接下来就很简单了。把用户输入的帐号密码与刚才你从数据库中查出来的帐号密码对比一下即可。token封装着用户的帐号密码,AuthenticationInfo封装着从数据库中查询出来的帐号密码。再往下追踪一下代码,最终到了下图中的核心区域。如果没有报异常,说明本次登录成功。
四、认证流梳理
首先将用户名、密码封装成token。
Subject门面获取到用户表示是token。传递到内部的SecurityManager中。
SecurityManager调用内部组件authenticator去验证。
authenticator将token传递到Realm中去。
最终由你将数据库中的数据查询出来,放到Readlm中去。shiro将会分析用户输入的token是否与数据库中查出来的一致。
再回来来看这张图,把下面的“5”理解成我们重写的Realm即可。
五、源码下载
本章节项目源码:点击我下载源码
----------------------------------------------------分割线-------------------------------------------------------
下一篇:第三节:Shiro对加密的支持
阅读更多:跟着大宇学Shiro目录贴
你看我都这么努力的分享知识给你了,鼓励一下又何妨O(∩_∩)O
大宇期待与你们共同进步!同时也非常感谢最近兄弟们的支持!