0%

Java:Shiro+SpringMVC的集成实践

摘要

Java:Shiro+SpringMVC的集成实践。

博客

原帖位于IT老兵博客

前言

个人感觉,Shiro的官网有一个问题,讲的不够清楚,尽管看上去好像讲的挺明白,但是我总是感觉很多地方不够清楚,事实上,在阅读了很多帖子之后,发现很多人都对这一点存在疑问,那就不是我一个人的问题了。

Shiro的官网缺乏完整的例子,而且我所处理的项目是Spring的项目,如何清楚地集成在一起,似乎还没有看到,很多地方都需要摸索,看了张开涛的博客,下面一样有很多人存有疑问。

之前研究这个,花了几天的时间研究理论,感觉自己已经明白了(这个感觉在另外一篇帖子《Java:Shiro的架构学习笔记》里面有提到),实际上是,纸上得来终觉浅,绝知此事要躬行。

这篇文章结合着自己的例子,把所理解到的东西做一个总结,以备日后查看,也给需要的朋友们一个参考。

正文

项目用的是XML配置,至于注解如何配置,暂时还没有时间去研究。

项目中定义一个spring-shiro.xml文件,配置在classpath里面,可以被系统读取到,这块涉及Spring读取配置文件的功能,官网是写在了applicationContext.xml文件里面,然后在web.xml里面定义filter,现在做项目,似乎已经很少用到这个web.xml文件,基本都是定义在spring-mvc.xml这个文件里面,这里给shiro单独定义了一个配置文件,原理是一样的。

先定义filter:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
</bean>

这个将会构造一个shiroFilter,参数是securityManager。

定义securityManager:

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="tokenRealm" />
    <property name="cacheManager" ref="cacheManager"></property>
    <property name="sessionManager" ref="sessionManager" />
    <property name="subjectFactory" ref="subjectFactory"/> 
    <property name="subjectDAO.sessionStorageEvaluator.sessionStorageEnabled" value="true"/>
    <!-- By default the servlet container sessions will be used. Uncomment 
        this line to use shiro's native sessions (see the JavaDoc for more): -->
    <property name="sessionMode" value="http"/>
</bean>

这里构造了securityManager,并且传递了6个参数给它,每个参数可以是自己写的继承类,也可以是默认的类,这里涉及一些业务隐私的问题,不能都贴出来了。

第一个tokenRealm是用于进行认证的组件。

<bean id="tokenRealm" class="xx.xx.xx.TokenRealm">
    <property name="credentialsMatcher" ref="credentialsMatcher"/>  
</bean>

参数是自定义的一个凭证匹配器。
这里需要覆写两个方法:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException 返回认证信息。

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 返回授权信息。
这个地方之前一直没有搞明白,是最让我困惑的地方,doGetAuthenticationInfo的第一个参数就是login方法送过来的token,一般这个token带有username和password,这里根据这个用户名去把数据库把用户的密码取出来,然后构造一个SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());返回,然后会交由匹配器去匹配,匹配器主要匹配第二个参数(原型是:SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)),即凭证是否相等。

而自定义的匹配器大体是下面这样,覆写匹配的函数(增加了缓存来保存尝试次数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override  
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String) token.getPrincipal();
AtomicInteger retryCount = loginRetryCache.get(username);
System.out.println("重试次数:" + retryCount);
if (retryCount != null && retryCount.intValue() >= maxRetryCount) {
throw new ExcessiveAttemptsException("username: " + username + " tried to login more than 5 times in period");
}

boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
//clear retry data
System.out.println("清除重试次数缓存");
if (retryCount != null) {
loginRetryCache.remove(username);
}
return true;
} else {
if (null == retryCount) {
retryCount = new AtomicInteger(1);
loginRetryCache.put(username, retryCount);
System.out.println("插入缓存,失败次数:" + retryCount);
} else if (retryCount.incrementAndGet() >= maxRetryCount) {
log.warn("username: " + username + " tried to login more than 5 times in period");
throw new ExcessiveAttemptsException("username: " + username + " tried to login more than 5 times in period");
}

retryCount = loginRetryCache.get(username);
System.out.println("认证失败,失败次数:" + retryCount);
return false;
}

}

在login完成后,Shiro其实会返回给客户端一个JSESSIONID,并且会在缓存中保存关于这个会话的一些信息,这些会话信息会被定期清理(由调度任务15分钟或者是下一次访问时判断是否过期)或者是由logout方法主动注销掉。

总结

初步总结了一下Shiro的用法,实践了一天,总结了一天,终于感觉搞明白了,使用Shiro的难度主要在于牵扯的类比较多,而且文档说的不是太清楚,需要自己反复地实践。这也可能说明它设计得很灵活,一般设计得很灵活的东西,都是不容易掌握,但是,一旦掌握了,就非常得方便。
这篇帖子还会不断更新,直到把这个地方的概念全部梳理清楚。

参考

https://shiro.apache.org/10-minute-tutorial.html
https://shiro.apache.org/static/1.3.0/apidocs/org/apache/shiro/authc/SimpleAuthenticationInfo.html
https://shiro.apache.org/architecture.html