# 1.配置
# 1.1 web.xml
web.xml需要增加如下配置:
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
# 1.2 shiro配置
shiro详细配置参见shiro.xml,其中主要配置如下:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/" />
<property name="unauthorizedUrl" value="/unauthorized" />
<property name="filters">
<map>
<entry key="user">
<bean class="com.chuanghai.easypm.application.security.EasyPMUserFilter"></bean>
</entry>
<entry key="logout">
<bean class="com.chuanghai.easypm.application.security.EasyPMLogoufilter"></bean>
</entry>
<entry key="backlog">
<bean class="com.chuanghai.easypm.application.security.EasyPMBacklogRequestFilter"></bean>
</entry>
<entry key="project">
<bean class="com.chuanghai.easypm.application.security.EasyPMProjectRequestFilter"></bean>
</entry>
<entry key="sfilter">
<bean class="com.chuanghai.easypm.application.security.EasyPMUserResRequestFilter"></bean>
</entry>
</map>
</property>
<property name="filterChainDefinitions" value="#{urlAuthcService.loadFilterChainDefinitions()}">
<!-- <value>
/login = authc
/logout = logout
/resources/**=anon
/admin=perms[sysset]
/**=user
</value> -->
</property>
</bean>
loginUrl:登录请求地址;此地址用于当请求资源需要登录时,会自动跳转到此设定地址。
successUrl:登录成功后默认跳转地址。
unauthorizedUrl:发生org.apache.shiro.authz.UnauthorizedException异常时跳转地址
filters:此处配置自定义的filter,其中的key会在下一项配置中用到。
filterChainDefinitions:
过滤器链定义;关于此定义,可以直接使用注释掉部分的定义形式,也可以使用urlAuthcService.loadFilterChainDefinitions(),当初使用服务动态生成定义是为了满足特定的需求,目前的需求已经可以不需要使用此服务进行动态生成,但目前保留了这样的配置,定义的主要内容在 fixed_auth_res.properties中。
# 1.3 自定义配置
fixed_auth_res.properties
此文件供urlAuthcService动态加载使用。目前文件内容如下:
/logout = logout
/resources/**=anon
/admin/**=authc,perms[other:sysset]
/api/backlog/**=user,backlog
/api/filter/**=user,sfilter
/project/**=user,project
/api/project/**=user,project
/invite =anon
/rejectInvite =anon
/register/** =anon
/resetpass/** =anon
其中anno,authc,perms均是shiro框架自带的一些filter,value多个之间是and的关系。还有其它默认的filter可以参见http://shiro.apache.org/web.html中Default filters中的列表。以下是使用默认的过滤器的说明,自定义的在"2.相关类及作用“中说明
logout:
anno:表示匿名,表示此请求不需要用户认证。
authc:表示用户必须经过登录认证。
perms:表明必须拥有某项特定权限
# 2.相关类及作用
com.chuanghai.easypm.application.security 包:
## 2.1 EasyPMBacklogRequestFilter
backlog相关请求的权限拦截,拦截api/backlog开头的相关请求。实现过程为在org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter的基础上继承扩展,通过读取请求参数或根据backlog.id读取相应对象获得project.id和backlog类型,根据project.id、backlog类型、httpmethod构建权限字符串,根据权限字符串判断是否拥有相关权限,
## 2.2 EasyPMLogoufilter
注销操作过滤器,在org.apache.shiro.web.filter.authc.LogoutFilter的基础上继承扩展,扩展唯一的作用是改变默认的注销后跳转地址到"/login",默认的是“/”。
## 2.3 EasyPMProjectRequestFilter
project相关请求的权限拦截,拦截/project/、api/backlog开头的相关请求,同样根据请求的类型(POST/GET/PUT/DELETE),判断需要的相应权限,同样在org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter的基础上继承扩展。
## 2.4 EasyPMUserFilter
在org.apache.shiro.web.filter.authc.UserFilter的基础上进行继承扩展,主要的作用是ajax请求如果没有权限,返回前端跳转标志,由前端进行跳转处理。
## 2.5 EasyPMUserResRequestFilter
在org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter的基础上进行继承扩展,主要作用是对于SearchFilter资源的请求,根据filter.id是否小于0(系统内置filter)以及用户的ID是否当前登录用户来确定请求是否有权限。
## 2.6 ShiroDBRealm
主要的作用是处理登录及权限读取
doGetAuthenticationInfo:登录验证处理。
doGetAuthorizationInfo:权限读取处理。
## 2.7 UrlAuthcService
此接口主要用于动态生成1.2配置文件中的filterChainDefinitions。主要的函数loadFilterChainDefinitions(),处理链定义包括三部分(有先后顺序):
1、固定部分,此部分读取1.3中的配置文件
2、动态部分,调用getDynaAuthRule()函数生成,
3、结束部分,目前为常量,定义为”/** =user“
由于目前的动态部分根据目前的需求已不使用,所以整个定义也可以不用此类生成,而用1.2配置文件中注释掉的部分那种形式予以定义,但目前保留此框架,用于未来需求变化时使用。
## 2.8 PermissionService
主要使用此服务的isPermitted方法进行权限判断,参数为权限字符串,具体参考权限项介绍。
## 2.9 BeforeBacklogChange
利用AOP对backlog资源相关操作进行权限判断。
## 2.10 BeforeProjectSetupChange
利用AOP对项目设置相关资源操作进行权限判断
## 2.11 BeforeSystemSetupChange
利用AOP对系统(团队)设置相关资源操作进行权限判断
# 3.权限项
目前所有的权限项处理可以综合参考数据库permission表以及 UserService中的getUserHasPermissions函数处理;
目前主要有三类权限:
1、backlog权限,定义的形式为”项目ID:backlog类型:动作“,例如”1:req:create“,即表明项目ID等于1的添加需求权限
2、项目设置权限,定义的形式为"项目ID:project:setup",后两项固定,第一项为项目ID。
3、系统(团队)设置权限,定义的形式为"other:sysset[:团队ID]",当线上版本时才有团队ID这一项。
# 4.权限拦截
## 4.1 URL拦截
URL的权限拦截主要通过Shrio框架的filter及相关自定义扩展filter实现,即主要由1.2配置文件中filterChainDefinitions的定义进行拦截,具体可以参考"2.相关类及作用”中相关filter的说明。
## 4.2 Service拦截
Service层权限拦截主要是利用Spring AOP前置通知的方式对需要控制权限的资源相关服务方法(save/update/delete)的操作进行拦截,主要通过com.chuanghai.easypm.domain.service.aop包中以下三个类实现
BeforeBacklogChange
BeforeProjectSetupChange
BeforeSystemSetupChange
AOP配置如下(bean.xml中):
<bean id="backlogChange" class="com.chuanghai.easypm.domain.service.aop.BeforeBacklogChange"/>
<bean id="systemChange" class="com.chuanghai.easypm.domain.service.aop.BeforeSystemSetupChange"/>
<bean id="projectChange" class="com.chuanghai.easypm.domain.service.aop.BeforeProjectSetupChange"/>
<!-- AOP配置 -->
<aop:config>
<!-- 配置切面aspect -->
<aop:aspect id="backlogChangeAspect" ref="backlogChange" order="3">
<aop:before
pointcut="execution(* com.chuanghai.easypm.domain.service.base.BacklogBaseService+.save*(..))"
method="beforeBacklogSave"
/>
<aop:before
pointcut="execution(* com.chuanghai.easypm.domain.service.base.BacklogBaseService+.update*(..))"
method="beforeBacklogUpdate"
/>
<aop:before
pointcut="execution(* com.chuanghai.easypm.domain.service.base.BacklogBaseService+.delete*(..))"
method="beforeBacklogDelete"
/>
</aop:aspect>
<aop:aspect id="systemChangeAspect" ref="systemChange" order ="4">
<aop:before
method="beforeSysSetChange"
pointcut="execution(* com.chuanghai.easypm.domain.service.app..*.save*(..))"
/>
<aop:before
method="beforeSysSetChange"
pointcut="execution(* com.chuanghai.easypm.domain.service.app..*.update*(..))"
/>
<aop:before
method="beforeSysSetChange"
pointcut="execution(* com.chuanghai.easypm.domain.service.app..*.delete*(..))"
/>
</aop:aspect>
<aop:aspect id="projectChangeAspect" ref="projectChange" order ="4">
<aop:before
method="beforePrjSetChange"
pointcut="execution(* com.chuanghai.easypm.domain.service.project..*.save*(..))"
/>
<aop:before
method="beforePrjSetChange"
pointcut="execution(* com.chuanghai.easypm.domain.service.project..*.update*(..))"
/>
<aop:before
method="beforePrjSetChange"
pointcut="execution(* com.chuanghai.easypm.domain.service.project..*.delete*(..))"
/>
</aop:aspect>
</aop:config>