Java:Shiro+SpringMVC 的集成实践

摘要

Java:Shiro+SpringMVC的集成实践。

博客

原帖位于IT老兵博客

前言

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

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。

然后需要在web.xml中定义:

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

可以参考这里,这里有点复杂,把过滤 filter 的工作代理给我们创建的这个类。

DelegatingFilterProxy is a class in Spring's Web module. It provides features for making HTTP calls pass through filters before reaching the actual destination. With the help of DelegatingFilterProxy, a class implementing the javax.Servlet.Filter interface can be wired into the filter chain.
DelegatingFilterProxy 是 Spring Web 模块,提供一些特征,来使 HTTP 请求通过这些 filter,然后再到达实际的目的地。

As an example, Spring Security makes use of DelegatingFilterProxy to so it can take advantage of Spring’s dependency injection features and lifecycle interfaces for security filters.
作为一个例子,Spring Security 就是利用 DelegatingFilterProxy 来利用 Spring 的依赖注入和生命循环接口。

DelegatingFilterProxy also leverages invoking specific or multiple filters as per Request URI paths by providing the configuration in Spring's application context or in web.xml.

原文是这么讲述 DelegatingFilterProxy 的作用的,最关键的是第二段,这样的话,它就像 Spring Security 一样,也能充分享受 Spring 的依赖注入的特征和生命周期接口(本身,Shiro 的出现好像就是实现轻量版的 Security,Security 可能有些太重了)。

再定义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)),即凭证是否相等。

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

@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
https://stackoverflow.com/questions/6725234/whats-the-point-of-spring-mvcs-delegatingfilterproxy
https://docs.spring.io/spring-security/site/docs/3.0.x/reference/security-filter-chain.html
https://shiro.apache.org/static/1.3.0/apidocs/org/apache/shiro/spring/web/ShiroFilterFactoryBean.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值