应用软件中通常包含用户的权限管理功能,往往不同应用系统对权限管理功能的需求不同,有自己的权限模型,权限的授权、鉴权策略。权限管理是相对独立的一个功能模块,由于功能需求不同、采用模型设计、实现技术等的不同,往往给设计开发人员带来很大的负担,开发出来的东西往往耦合性大、不易扩展维护,给项目带来很大风险。为适应软件开发组件模块开发趋势,把它设计成通用、灵活复用嵌入应用系统的组件模块不是一件容易的事情。业界曾提供了许多通用权限管理的组件或解决方案。参照一些资源,以及我的一些开发项目经验,试图探索一些权限组件设计开发的一些思路,也希望能得到阅读本文的意见和建议,持续改进完善它。现在我主要从以下方面介绍:1)权限模型;2)基础框架技术组件
权限模型
包括授权模型、鉴权模型(也叫权限验证模型)。授权模型有DAC、MAC、ACL、RBAC。鉴权模型,给出一个图解如下

比较著名的授权模型是RBAC(基于角色的访问控制)。这个资料很多,不用详细说了。主要涉及的实体有用户users(USERS)? 、角色roles(ROLES)? 、目标objects(OBS)? 、操作operations(OPS)? 、许可权permissions(PRMS)? 。
权限技术组件选型
基础框架的技术组件,比较著名的应该是Acegi Security(以下简称Acegi),它是一个能为基于Spring的企业应用提供强大而灵活安全访问控制解决方案的框架,Acegi已经成为Spring官方的一个子项目,所以也称为Spring Security。Acegi提供的权限管理方面功能可以说非常全面,但是它的设计比较细致、复杂,需要一定时间学习和掌握它的框架结构和使用原理,才能得到比较好应用。属于比较重量级的组件,如果权限方面的有更多特性化的业务需求,需要定制扩展该组件时,就需要非常熟悉掌握到一定程度,甚至需要修改扩展Acegi源码,如果不够熟悉组件架构原理,就会改不好,甚至引来一些不必要的麻烦。对于开源的组件,我个人建议是对于设计的重量级的组件,如果应用系统目前用到的功能没有那么全面,只用部分功能的话,就可以了解到它的架构机制原理后,把需要使用的部分功能从开源组件中抽取出来,整合舍弃一些不太用到的源码,简单化,封装成一个相对轻量级的组件,这样既可以满足应用系统的功能,也能充分利用开源组件的框架机理(毕竟开源组件是经过很多项目的检验,可以借鉴的,很多地方比我们个人考虑更成熟)。这样形成的轻量级组件植入我们的应用系统,变得更可控,更容易维护、灵活扩展。
Acegi实现机制可以参看文档http://blog.csdn.net/yan_dk/article/details/7228167。
总体来说,Acegi实现还是功能集成度高、复杂、重量级的。笔者建议还是把它拆分成不同的功能部分,根据需要分别来用不同组件实现比较好,比如认证处理我们可以采用SSO组件就可以,权限资源拦截我们可以自己编写权限资源访问匹配方法,这样比较灵活一些。下面谈一个权限资源拦截的一个设计、编码实例。
设计思路主要是:
1.一个单例(AuthorityContextSingleton):保存当前用户拥有的所有权限资源路径,用于在权限资源拦截时提供可用权限资源;
2.一个过滤器(AuthorityInterceptorFilter):对请求资源进行拦截,根据上述单例中保存的用户拥有的权限资源路径进行匹配,如果不能正确匹配,就属于无权访问资源,进行拒绝访问的处理。
范例代码如下:
/**
* 权限资源上下文单例
* @author yandk
* @date Jan 30, 2012
*/
public class AuthorityContextSingleton {
protected Log log = LogFactory.getLog(getClass());
private LoginService loginService;
private String authoritySwitch;//权限资源拦截开关,true启用;false不启用。
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
public void setAuthoritySwitch(String authoritySwitch) {
this.authoritySwitch = authoritySwitch;
}
public String getAuthoritySwitch() {
return authoritySwitch;
}
private Map<String,List> mapSecurityResource=new HashMap<String,List>();//安全资源映射表,包含(操作员编码,安全资源路径)的键值对。
// private volatile static AuthorityContextSingleton authorityContextSingleton ;
private AuthorityContextSingleton(){
}
synchronized public void setSecurityResource(Oper oper){
List<String> list_funcPath= new ArrayList<String>();
String operName = oper.getOperName();
// 判断当前用户是否是超级用户角色,若是则开放所有访问资源,*.do
List<Role> list_role= oper.getRoleList();
for (Role role : list_role) {
String roleType = role.getRoleType();
if( 是超级管理员角色){//管理员角色类型是超级管理员时,拥有所有权限
list_funcPath.add("*.do");
mapSecurityResource.put(operName , list_funcPath);
return;
}
}
// 取得参数用户的权限资源
list_funcPath=loginService.getFuncPath(oper);
// 将参数用户及其权限资源存入安全资源映射表
mapSecurityResource.put(PubConstant.user_authrity_Key_prefix+operName, list_funcPath);
}
public Map<String, List> getMapSecurityResource() {
return mapSecurityResource;
}
|
注:本单例类通过spring注入上下文使用。
/**
* 安全资源访问的拦截控制:用户访问系统时,拦截控制只能访问被授权的资源(功能路径),如果访问未授权资源,则提示拒绝访问
* @author yandk
* @date Jan 29, 2012
*/
public class AuthorityInterceptorFilter implements Filter {
protected Log log = LogFactory.getLog(getClass());
public final static String USERDEFINE_FUNCPATH_INIT_PARAM = "userdefine.open.funcpath";//用户自定义的开放资源(url功能路径)
private String userdefine_funcpath;//用户自定义的开放资源-值
@Override
public void init(FilterConfig config) throws ServletException {
userdefine_funcpath = config.getInitParameter(USERDEFINE_FUNCPATH_INIT_PARAM);
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain) throws IOException, ServletException{
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = ((HttpServletRequest) request).getSession();
HttpServletResponse httpResponse = (HttpServletResponse)response;
try {
ServletContext servletContext = httpSession.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
AuthorityContextSingleton authorityContextSingleton = (AuthorityContextSingleton)ctx.getBean("authorityContextSingleton");
if("true".equals( authorityContextSingleton.getAuthoritySwitch())){//如果权限资源拦截开关未启用,则不拦截
String requestPath = httpRequest.getRequestURI();
String ls_requestPath = UrlUtils.buildFullRequestUrl(httpRequest.getScheme(), httpRequest.getServerName(), httpRequest.getServerPort(), requestPath, null);
Oper oper = (Oper) httpSession.getAttribute("oper");
if(oper!=null && userdefine_funcpath!=null){//在登录之后进行权限资源的拦截处理
// String operCode =oper.getOperCode();
String operName = oper.getOperName();
// 判断请求路径是否与设置参数userdefine_funcpath相匹配,若匹配可以开放访问
PathMatcher matcher = new AntPathMatcher();
String userdefinePaths[] = null;
userdefinePaths = userdefine_funcpath.split(",");
boolean flag;
flag = matchRequestPath(matcher,ls_requestPath,userdefinePaths);
if (flag) {//若匹配开放资源,则不拦截
log.info(" AuthorityInterceptorFilter request path '"+ls_requestPath+"'is matched,filter chain will be continued.");
}else{
// authorityContextSingleton.setSecurityResource(operCode);//aa
Map<String,List> mapSecurityResource=authorityContextSingleton.getMapSecurityResource();
List list_funcPath = mapSecurityResource.get(PubConstant.user_authrity_Key_prefix+operName);
if(list_funcPath!=null&&!list_funcPath.isEmpty()){
Object[] funcPaths = list_funcPath.toArray();
boolean flag2;
flag2= matchRequestPath(matcher,ls_requestPath,funcPaths);
if(!flag2){
httpResponse.sendRedirect(httpRequest.getContextPath()+"/accessDenied.jsp");
return;
}
}
}
}
chain.doFilter(request, response);
}
} catch (Exception ex) {
log.debug("SecurityResourceInterceptorFilter: " + ex.toString());
ex.printStackTrace();
}
}
private boolean matchRequestPath(PathMatcher matcher,String requestPath,Object[] resourcePaths){
boolean flag=false;
for (Object resourcePath : resourcePaths) {
flag = matcher.match("**/"+(String)resourcePath, requestPath);
if (flag) {
flag=true;
break;
}
}
return flag;
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
|
在web.xml文件中写入对过滤器组件的引用,如下
...
<!-- 安全资源拦截控制 -->
<filter>
<filter-name>authorityInterceptorFilter</filter-name>
<filter-class>cn.ceopen.bss.pub.login.util.AuthorityInterceptorFilter</filter-class>
<init-param>
<param-name>userdefine.open.funcpath</param-name>
<param-value>
**/pub/login.do,**/pub/leftMenu.do
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>authorityInterceptorFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
...
|
这样,在就可以实现对权限资源的拦截控制。