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