陈小虎,太平洋网络,2015年10月
作为一个快速开发的框架和工具,给力对于实体对象自动生成了增删改查的基本UI功能,希望能尽可能的减少开发的重复工作,取得了不错的效果。
最初设计的思路很简单,对于实体对象,提供基本的功能,当这些功能不够用时,开发可以通过代码生成工具生成代码,经过改进后覆盖基本功能。对于权限部分,因为业务的千差万别,不好提供简单的方法,所以需要每个应用自己处理,代码生成工具在应用里面生成了AuthFilter这个过滤器,并实现了对于后台权限日志等基本功能的权限限制代码,希望开发人员能够自己添加代码,处理其他部分的权限问题。
遗憾的是碰到了两个问题,首先,由于设计的疏忽,AuthFilter的代码设计成了黑名单模式,就是说对于一个功能,除非加了限制,否则谁都能访问;其次,对于框架的培训和培训结果的确认不够彻底,导致有些开发不清楚这部分的设计。以至于新加了实体可能忘记加权限限制。
基于以上原因,需要对这部分重新设计并加强培训来解决这个问题,因此也就有了本文。
首先,对于AuthFilter的设计改为白名单方式,当然这里有些折中,就是缺省情况,所有的修改权限,必须是系统的超级管理员才能使用,所有的查看权限,必须是本应用的后台登陆用户才能使用。相信这种设定对大部分情况都是合理的。
其次,对于需要特殊处理的情况,可以通过AuthFilter里面的hasRight方法来进行处理,可以指定:0 - 不处理(采用缺省处理),1 - 没有权限,2 - 有权限 这三种情况来简化事情。用白名单为主的方式来解决问题,避免无意的遗漏权限。
最后,如果这套方案完全不能满足,还可以自己Override过滤器方法,完全定制,当然这种情况应该比较少见。
现在的情况是自动生成的代码AuthFilter里面会漏了增加权限限制。代码模板如下:
/// 注意!!这种方法有风险,需要改进为新模式!!!
/// 注意!!这种方法有风险,需要改进为新模式!!!
/// 注意!!这种方法有风险,需要改进为新模式!!!
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
String uri = req.getRequestURI();
Env env = EnvUtils.getEnv();
if (LOG.isDebugEnabled()) {
LOG.debug("AuthFilter process: " + uri);
}
HttpMethod method = env.getHttpMethod();
GeliAuthFacade authFacade = env.getBean(GeliAuthFacade.class);
String adminPrefix = env.getServletContext().getContextPath() + "/admin";
if (uri.startsWith(adminPrefix + "/geli") && !uri.endsWith("geliuser/select.do")) {
if (! authFacade.isAdmin()) {
if (method == HttpMethod.POST) {
sendAuthFail(resp, true);
} else {
sendAuthFail(resp, false);
}
return;
}
}
// Please process application auth here:
// ...
// ...
chain.doFilter(request, response);
}
由于只对 /admin/geli*** 的url做了限制,用户自己增加的实体需要自己家限制,但是示例代码页比较麻烦,所以很难让大家搞好。
改进后的模板目标更明确,就是专注于限制后台自动提供的功能。模板如下:
static final String CREATE_DO = "create.do";
static final String UPDATE_DO = "update.do";
static final String DELETE_DO = "delete.do";
@Override
public int hasRight(HttpServletRequest req) {
Env env = EnvUtils.getEnv();
GeliAuthFacade authFacade = env.getBean(GeliAuthFacade.class);
// examples...
if (matchActions("sales", req, CREATE_DO, UPDATE_DO, DELETE_DO)) {
return authFacade.hasRight(GeliFunction.read("sales_maint")) ? HAS_RIGHT : HAS_NOT_RIGHT;
}
return DEFAULT_RIGHT;
}
// check request uri match ${contextPath}/admin/${entityName}/${one of actions}
boolean matchActions(String entityName, HttpServletRequest req, String ... actions) {
String uri = req.getRequestURI();
Env env = EnvUtils.getEnv();
String uriPrefix = env.getServletContext().getContextPath() + "/admin/" + entityName.toLowerCase() + '/';
for (String action : actions) {
if (uri.startsWith(uriPrefix + action)) {
return true;
}
}
return false;
}
大部分情况只要按照example的模式就可限制了,其他情况可以自己特殊处理。
example提供的例子表示对于实体Sales,相关的 create.do | update.do | delete.do 如果拥有sales_maint(销售维护)权限,可以使用,否则按照系统的缺省模式(只有系统超级管理员才能使用)。
应用非常简单,而且就少数几个用户使用。直接将用户设置为超级管理员,不用考虑权限问题,系统缺省方案会直接满足需求。
简单,但是只适用于特定的情况。
感觉不错,啥都不用做啊,但是不会每次这么好运气:)
对于大部分情况,应该会有超级管理员以外的用户角色,但是还是有很多的权限只给超级管理员用,对于普通用户要使用的功能,在 hasRight 方法里面进行特别允许就可以了。1.3节的example很好的说明了这种情况。
对于复杂的大型应用,当后台权限系统很复杂时,最好自己进行定制开发,这里只提供两种思路,一种就是将哪些url的哪些http方法(GET、POST等)对应哪些功能做一个模块,进行配置。
更复杂的就要完全定制了。因为很权限和数据还有关系,就要使用资源的概念了。
所有使用 geli-2.x 的应用都需要升级,升级方式如下:
url-pattern从 *.do 到 /admin/* 以提高前台的效率