1
Shiro Security
Calvin Xiao edited this page 2013-11-14 19:13:33 -08:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Overview

1.权限模型

除了够简单,Shiro与SpringSecurity的不同在于支持权限判断与角色判断两套API,代码里既可以简单的判断hasRole("administrator"),也可以更准确的判断hasPermission("Order:Approve")。

实际上,大部分的应用都采用后者,角色只用来组织管理一堆权限的集合,而不会在最终代码(JSP, URL Filter, Java Code)里用作直接的权限判断。

而SpringSecurity和JAAS却都只支持基于角色判断的API,所以在以前的代码里都会别扭的把Role当Permission来使用("ROLE_User:Approve")。

2.用户权限构造

另一个不同的地方是, SpringSecurity用户的权限在login时就构造好并保存在session中。如果中途改变用户或角色的权限,只能想办法把他踢走重登录才能生效。 而Shiro会在每次检验权限时才查询权限。当然,为了性能考虑,Shiro提供了起码两种权限缓存方案,详见后面的Caching小节。

3.权限控制

权限控制分为页面内容,URL,和Java代码三层。 老规矩是页面内容用taglib,URL的控制在applicationContext.xml里配置,java代码用Annotation。

页面内容的控制保证了善良的用户不会点击自己无权限的功能。而对于恶意用户,在URL还是Java层(又分Controller或Service两层)进行校验可按实际需求而定。

在quickstart中,因为所有JSP都已经在WEB-INF/下保护起来,不可能被直接访问,所有的页面访问都要经过Controller,所以使用Annotation进行恶意用户保护达到的效果更好,URL级别只做用户是否登录的限制。

Step by Step

###Step1. 创建一个login.jsp 默认的input框名字应该是userName,password和rememberMe。

如果登录出现异常,它会放一个叫"shiroLoginFailure"的requestAttribute, 内容则是Exception的类名, showcase中的login.jsp演示了对不同的异常显示不同的出错信息。

注意实际的login和logout操作都是由配置在applicationContext.xml中的shiroFilter来完成的,所以即使是在SpringMVC里写一个打酱油的Controller,它也只应该负责显示初始的login.jsp和登录如果失败再次显示login.jsp。

###Step2. 实现一个AuthorizingRealm 查找用户AuthenticationInfo的函数,包括用户名、密码及Salt。 查找权限的函数,Role与Permission均可,仅在CacheManager缓存不存在时被调用。 一般密码不是明文存储的,需要在构造函数里指明密码匹配器,比如现在比较流行的,使用salt的,1024次 sha-1 hash 如果想在页面显示当前用户的更多信息,可以构造一个特定的用户对象,而不仅仅是用户的loginName,还可以包含id,display name等等.

###Step3. web.xml里ShiroFilter统统管起来 用Shiro filter管住/,注意这里spring的DelegatingFilterProxy,需要在applcationContext-.xml里定义一个与filter-name 同名的bean

###Step4. applicationContext-*.xml中的配置 最重要是ShiroFilter中filterChainDefinitions的配置,配置从上到下,以最上面的为准 详细的解释在这里: http://shiro.apache.org/web.html#Web-defaultfilters, quickstart中applicationContext-shiro.xml的配置如下:

/login = authc
/logout = logout
/static/** = anon
/** = user

authc filter 监听/login的POST请求(Get的不管)进行登录认证。如果成功就跳回前一个页面 或者上面配的successfulUrl,如果失败就forward到login page,不过这时候就是POST的,那个打酱油的LoginController要注意。

logout filter 负责监听/logout,所以把"退出登录"的链接设为/logout即可

/static/** 这些静态图片都不需要监控,轻轻放过,然后后面几个link就需要permission控制了。

/** = user 最后保证除上述url外的所有url都必须是已登录的,未登录的用户访问会跳转到登录页面。

另外RememberMe默认由cookie实现,有效时间一年,如果需要改变,配置新的RememberMeManager并注入SecurityManager.

注意URL Pattern里用到的是两颗星,才能实现任意层次的全匹配

其他可选的设置

/admin/** = roles[admin]
/rest/users = authcBasic

###Step5. JSP内容控制 http://shiro.apache.org/web.html#Web-taglibrary, 提供了那些必然有的标签,如表示只有未登录才显示(显示请登录),表示只有登录了才显示(显示请退出),检查权限等等.

比较有趣是显示当前用户的标签,你可以在reaml登录时自定义一个用户对象(见step2),然后用显示下面的标签想显示的属性。

<principal property="firstName"/> 

Shiro默认没有提供hasAnyPermissions的tag,因此Springside-core中另外提供了一个增强的版本,jsp中定义taglib时,url改为http://www.springside.org.cn/tags/shiro 即可

###Step6. 方法级控制 在applicationContext.xml(Service层)或spring-mvc.xml中加入AOP定义,见quickstart的spring-mvc.xml

在方法上加入

    @RequiresPermissions("User:Edit")

如果要实现多个Role之间Or的关系,可以写成这样:

    @RequiresRoles(value = { "Admin", "User" }, logical = Logical.OR)

如果要在controller上用annotation控制权限,spring-mvc.xml需要如下配置:

1.因为applicationContext-shiro.xml中的AOP管不到controller,因此需要在spring-mvc.xml中加入相同配置

2.添加统一的异常处理,用SimpleMappingExceptionResolver映射UnauthorizedException到eror/403.jsp, 因为出现此错误的用户基本都是恶意用户,因此403.jsp简单显示错误即可。

SpringSide Core模块

1. ShiroTestUtils

有時候单元测试中会使用到Shiro Context中的用户,ShiroTestHelper提供了方法绑定和清除当前线程的Subject.
但Subject本身也是不好创建的, Shiro的文档里推荐大家用MockObject,ShiroTestUtils提供了实现.

##Tips

1. 各种Bean找不到的问题。

A. 与Spring Data JPA同时使用时,ShiroFilterFacotryBean的BeanPostProcessor报找不到XXDao类,可能是Spring Data JPA的BeanPostProcessor还没完成自己的任务吧。一个workaround方法是在shiroDbRealm的定义里加上depends-on="缺失Dao"的定义,强制Dao被初始化。 B. 用AOP控制方法时,把DefaultAdvisorAutoProxyCreator的proxyTargetClass属性设为true,与事务AOP一起使用cglib。

2. Cache

如前所述,Shiro会在每次鉴权时重新获取用户的授权信息,一般需要用Cache缓存起来。

默认的Cache是JVM内的,不会过期,这就需要在用户和角色管理中,显式的插入代码在修改用户/角色后清理缓存。如果懒一点,可以使用Ehcache单机版,定义120秒自然过期。另外,如果是集群,又希望即时更新,就又需要使用Ehcache 集群方案如Ehcache-RMI,又要插入代码即时更新。 但个人还是推荐使用120秒自然过期最为简单方便,见showcase中的示例。

	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="shiroDbRealm" />
		<property name="cacheManager" ref="shiroEhcacheManager" />
	</bean>
	<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:security/ehcache-shiro.xml"/>
	</bean>