Skip to content

Shiro Security

Calvin Xiao edited this page Nov 15, 2013 · 1 revision

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>