Spring 整合Shiro 并扩展使用EL表达式的实例详解
|
Shiro是一个轻量级的权限控制框架,应用非常广泛。本文的重点是介绍Spring整合Shiro,并通过扩展使用Spring的EL表达式,使@RequiresRoles等支持动态的参数。对Shiro的介绍则不在本文的讨论范围之内,读者如果有对shiro不是很了解的,可以通过其官方网站了解相应的信息。infoq上也有一篇文章对shiro介绍比较全面的,也是官方推荐的,其地址是https://www.oudahe.com/p/84/。 Shiro整合Spring 首先需要在你的工程中加入shiro-spring-xxx.jar,如果是使用Maven管理你的工程,则可以在你的依赖中加入以下依赖,笔者这里是选择的当前最新的1.4.0版本。 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> 接下来需要在你的web.xml中定义一个shiroFilter,应用它来拦截所有的需要权限控制的请求,通常是配置为/*。另外该Filter需要加入最前面,以确保请求进来后最先通过shiro的权限控制。这里的Filter对应的class配置的是DelegatingFilterProxy,这是Spring提供的一个Filter的代理,可以使用Spring bean容器中的一个bean来作为当前的Filter实例,对应的bean就会取filter-name对应的那个bean。所以下面的配置会到bean容器中寻找一个名为shiroFilter的bean。 <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <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> 独立使用Shiro时通常会定义一个org.apache.shiro.web.servlet.ShiroFilter来做类似的事。 接下来就是在bean容器中定义我们的shiroFilter了。如下我们定义了一个ShiroFilterFactoryBean,其会产生一个AbstractShiroFilter类型的bean。通过ShiroFilterFactoryBean我们可以指定一个SecurityManager,这里使用的DefaultWebSecurityManager需要指定一个Realm,如果需要指定多个Realm则通过realms指定。这里简单起见就直接使用基于文本定义的TextConfigurationRealm。通过loginUrl指定登录地址、successUrl指定登录成功后需要跳转的地址,unauthorizedUrl指定权限不足时的提示页面。filterChainDefinitions则定义URL与需要使用的Filter之间的关系,等号右边的是filter的别名,默认的别名都定义在org.apache.shiro.web.filter.mgt.DefaultFilter这个枚举类中。 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/home.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitions"> <value> /admin/** = authc,roles[admin] /logout = logout # 其它地址都要求用户已经登录了 /** = authc,logger </value> </property> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="realm"/> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 简单起见,这里就使用基于文本的Realm实现 --> <bean id="realm" class="org.apache.shiro.realm.text.TextConfigurationRealm"> <property name="userDefinitions"> <value> user1=pass1,role1,role2 user2=pass2,role2,role3 admin=admin,admin </value> </property> </bean> 如果需要在filterChainDefinitions定义中使用自定义的Filter,则可以通过ShiroFilterFactoryBean的filters指定自定义的Filter及其别名映射关系。比如下面这样我们新增了一个别名为logger的Filter,并在filterChainDefinitions中指定了/**需要应用别名为logger的Filter。
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="/home.jsp"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<property name="filters">
<util:map>
<entry key="logger">
<bean class="com.elim.chat.shiro.filter.LoggerFilter"/>
</entry>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/admin/** = authc,logger
</value>
</property>
</bean>
其实我们需要应用的Filter别名定义也可以不直接通过ShiroFilterFactoryBean的setFilters()来指定,而是直接在对应的bean容器中定义对应的Filter对应的bean。因为默认情况下,ShiroFilterFactoryBean会把bean容器中的所有的Filter类型的bean以其id为别名注册到filters中。所以上面的定义等价于下面这样。 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/home.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filterChainDefinitions"> <value> /admin/** = authc,logger </value> </property> </bean> <bean id="logger" class="com.elim.chat.shiro.filter.LoggerFilter"/> 经过以上几步,Shiro和Spring的整合就完成了,这个时候我们请求工程的任意路径都会要求我们登录,且会自动跳转到loginUrl指定的路径让我们输入用户名/密码登录。这个时候我们应该提供一个表单,通过username获得用户名,通过password获得密码,然后提交登录请求的时候请求需要提交到loginUrl指定的地址,但是请求方式需要变为POST。登录时使用的用户名/密码是我们在TextConfigurationRealm中定义的用户名/密码,基于我们上面的配置则可以使用user1/pass1、admin/admin等。登录成功后就会跳转到successUrl参数指定的地址了。如果我们是使用user1/pass1登录的,则我们还可以试着访问一下/admin/index,这个时候会因为权限不足跳转到unauthorized.jsp。 启用基于注解的支持 基本的整合需要我们把URL需要应用的权限控制都定义在ShiroFilterFactoryBean的filterChainDefinitions中。这有时候会没那么灵活。Shiro为我们提供了整合Spring后可以使用的注解,它允许我们在需要进行权限控制的Class或Method上加上对应的注解以定义访问Class或Method需要的权限,如果是定义中Class上的,则表示调用该Class中所有的方法都需要对应的权限(注意需要是外部调用,这是动态代理的局限)。要使用这些注解我们需要在Spring的bean容器中添加下面两个bean定义,这样才能在运行时根据注解定义来判断用户是否拥有对应的权限。这是通过Spring的AOP机制来实现的,关于Spring Aop如果有不是特别了解的,可以参考笔者写在iteye的《Spring Aop介绍专栏》。下面的两个bean定义,AuthorizationAttributeSourceAdvisor是定义了一个Advisor,其会基于Shiro提供的注解配置的方法进行拦截,校验权限。DefaultAdvisorAutoProxyCreator则是提供了为标注有Shiro提供的权限控制注解的Class创建代理对象,并在拦截到目标方法调用时应用AuthorizationAttributeSourceAdvisor的功能。当拦截到了用户的一个请求,而该用户没有对应方法或类上标注的权限时,将抛出org.apache.shiro.authz.AuthorizationException异常。 <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> 如果我们的bean容器中已经定义了 Shiro提供的权限控制注解如下: RequiresAuthentication:需要用户在当前会话中是被认证过的,即需要通过用户名/密码登录过,不包括RememberMe自动登录。 RequiresUser:需要用户是被认证过的,可以是在本次会话中通过用户名/密码登录认证,也可以是通过RememberMe自动登录。 RequiresGuest:需要用户是未登录的。 (编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
