http://blog.youkuaiyun.com/netv/article/details/6526142
一、管理什么,控制什么
我们在做系统开发的时候都离不开后台管理,我们要管理的非业务对象有哪些呢?根据我个人的经验总结如下:
1. 用户管理
2. 组织机构管理
3. 职务管理
4. 功能菜单
5. URL管理
6. 角色管理
二、如何管理
1. 用户管理分为前端用户和后台用户,可以分为两个数据表来保存,也可以使用一个标志位来判断,我个人选择后者,便于对用户进行统一的行为处理:统一的权限控制。。。
在登录时分为前端用户登录和后台用户登录,在Session中保存的是不同的对象。
用户管理和部门员工管理是两个概念,员工是Employee,只有在员工需要使用此系统时才分配有相对应的用户账号。
2. 组织机构管理,是通用的树型结构管理。要支持树结构的移动操作,排序操作,同时组织机构管理可以有多个树,这样的设计有利于集团公司系统的支持。组织机构的概念是系统里用户身份对应的组织机构,是用来做数据范围和权限控制的。
3. 职务管理,是通用的树型结构管理,每个职务都要有所属的组织机构。每个用户只有一个对应的职务。现实中的公司组织机构也是每个岗位都是明确的,当身兼多职时也是每次以不同的身份来操作业务,在系统中可以分配多个用户,或可通过给其中一个用户分配不同的权限来完成设置。
三、如何设计权限控制
我们在系统中要进行权限控制的对象有哪些呢?总结如下:
1. 系统中的功能操作,在WEB系统中对应到每一个URL
2. 系统中的功能菜单显示
3. 系统中的业务数据范围,即不同的用户能看到的业务数据是不一样的,这就需要业务数据中的组织机构关联属性来判断。是本人产生的业务数据?本部门的业务数据?跨部门的业务数据?
4. 系统中的业务对象的特殊字段/属性控制,即不同的用户可能看到的信息范围不一样,某些字段或属性只有特殊用户才能看到或操作。
以上是我们在系统中进行权限控制涉及到的资源对象,而权限控制的行为主体则是针对系统中的用户了。为系统中不同的用户授予不同的资源访问权限,并在对资源访问时进行权限的验证。
1.针对URL的授权与鉴权
现在我们引入角色对象,并将角色对象和系统中的URL关联起来,可以极大地方便我们的授权操作,只要将不同的角色授予不同的用户就可以了。但是如果针对系统中的每一个用户都进行授权还是太琐碎了,因为用户是动态变化的,经常需要增加与删除。这样我们引入了针对用户职务的授权,只要用户与不同的职务关联,就可以得到不同的授权信息。而职务的稳定性是优于用户的。这里的职务就相当于用户代理和用户分组的作用。同时针对用户也是可以做授权操作的,在进行权限验证时要将职务与用户本身的授权合并起来判断。同时我们在设计是每个URL都是对应一个业务对象的操作,我们可以增加对URL中的ID参数的授权,这样就可以控制到业务对象中的每一条记录数据。
2.针对功能菜单的授权与鉴权
同上述道理一样,我们可以针对用户和职务进行相应功能菜单显示的授权。
3.针对系统中业务数据范围的控制
这就涉及到用户的组织机构信息,在用户编辑业务数据时,要将相应的用户组织机构信息保存到业务数据中。然后在系统中我们针对用户和职务进行组织机构中数据范围的授权,在用户读取业务数据时进行组织机构范围的判断。
4.针对系统中业务数据的特殊字段控制
同上述,在系统中我们需要将相应的字段范围授权给相应的用户或职务。
通过以上的分析和设计,我们总结一下:
系统中的授权对象有:用户和职务,其中每个用户都只能关联一个职务。
系统中需要权限控制的资源对象有:URL,功能菜单,业务数据范围(所属组织机构),业务数据特殊字段。
需要建模的数据对象有:User, Position<TreeNode>, Organization<TreeNode>, MenuResource<TreeNode>, UrlResource, Role, RoleUrl, UserRole, PositionRole, UserMenu, PositionMenu, DataScope, UserDataScope, PositionDataScope, FieldScope, UserFieldScope, PositionFieldScope
四、何时进行鉴权操作
系统中的授权操作没什么分岐,只要明确了授权的对象及需要权限控制的资源对象即可进行相应的设计,那么我们在什么时候进行鉴权操作呢?
在Java的Web系统开发中的实现上,我们有如下的总结:
1. 在URL的控制上,我们可以用AuthenticateFilter来实现,首先获得访问系统的URL,再获得访问系统的用户信息,其中也包括匿名用户,可以将匿名用户指定为ID为-1的用户,并对其进行相应的授权。通过用户与职务信息查询其合并的授权中是否允许对此URL访问。
2. 在功能菜单的控制上,我们可以使用MenuFilter来实现,首先获得访问系统的用户信息,其中也包括匿名用户,可以将匿名用户指定为ID为-1的用户,并对其进行相应的授权。通过用户与职务信息查询其合并的授权中查询出所有功能菜单。
3. 在业务数据范围的控制上,可以建立一个查询业务数据的基类,并在基类的查询方法中增加检查数据范围的方法。
4. 在业务数据的特殊字段的控制上,可以使用具体的页面JSP标签库和相应的操作代码逻辑来判断。
五、部分代码示例
此部分代码是基于Struts2+Spring+Hibernate的实现,其中URL的控制是针对Struts2中的Action映射来实现的,如果是JSP的访问则直接使用JSP的文件名称控制。
1. AuthenticateFilter实现
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpRequest.setAttribute("sessionId", httpRequest.getSession().getId());
String url = parseUrl(httpRequest);
if (null != url && !SystemUtils.isExempt(url)) {
Passport user = SystemUtils.getUser(httpRequest, isAdmin());
boolean loginRedirect = false;
if (null == user && !mustLogin()) {
user = new Passport();
user.setId(-1L);
user.setUserName("anonymous");
loginRedirect = true;
}
if (null != user) {
boolean authenticated = false;
if (SystemUtils.isExempt(user)) {
authenticated = true;
} else {
RoleUrl[] authority = dao.getAuthority(user, url);
for (int i = 0; i < authority.length; i++) {
if (authority[i].isNegative()) {
authenticated = false;
break;
}
String idName = authority[i].getUrl().getIdName();
if (null != idName && !"".equals(idName.trim())) {
String id = httpRequest.getParameter(idName);
if (null != id && !"".equals(id.trim())) {
String idValues = authority[i].getIdValues();
String[] values = idValues.split("[, ;/t]");
for (int j = 0; j < values.length; j++) {
if (id.equals(values[j])) {
authenticated = true;
break;
}
}
}
} else {
authenticated = true;
}
}
}
if (!authenticated) {
if (loginRedirect) {
httpRequest.getSession().setAttribute(SystemUtils.REFERER_KEY, SystemUtils.getRequestURL(httpRequest));
httpResponse.sendRedirect(request.getServletContext().getContextPath() + getLoginRedirect());
} else
request.getServletContext().getRequestDispatcher(getUnauthorizedJsp())
.forward(httpRequest, httpResponse);
return;
}
} else {
httpRequest.getSession().setAttribute(SystemUtils.REFERER_KEY, SystemUtils.getRequestURL(httpRequest));
httpResponse.sendRedirect(request.getServletContext().getContextPath() + getLoginRedirect());
return;
}
}
try {
chain.doFilter(request, response);
} catch (DataScopeException e) {
request.setAttribute("message", e.getMessage());
request.getServletContext().getRequestDispatcher(getUnauthorizedJsp()).forward(httpRequest, httpResponse);
} catch (UnauthorizedException e) {
request.setAttribute("message", e.getMessage());
request.getServletContext().getRequestDispatcher(getUnauthorizedJsp()).forward(httpRequest, httpResponse);
}
}
2. MenuFilter实现
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
Long adminId = SystemUtils.getSystemAdminId();
Long menuRootId = SystemUtils.getSystemMenuId();
Passport user = SystemUtils.getUser(httpRequest, true);
if (null != user) {
// load menus
Collection<TreeNode<?>> menus = null;
if (adminId.equals(user.getId())) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("root.id", menuRootId);
params.put("ORDERBY", " treeOrder asc ");
menus = manager.query(MenuResource.class, params, 0, -1);
} else {
Map<String, Object> params = new HashMap<String, Object>();
params.put("menu.root.id", menuRootId);
params.put("user.id", user.getId());
params.put("ORDERBY", " menu.root.id, menu.treeOrder asc ");
List<MenuResource> userMenus = manager.query(UserMenu.class, "menu", params, 0, -1);
menus = new TreeSet<TreeNode<?>>(new Comparator<TreeNode<?>>() {
@Override
public int compare(TreeNode<?> o1, TreeNode<?> o2) {
return o1.compareTo(o2);
}
});
menus.addAll(userMenus);
if (null != user.getPosition()) {
params.clear();
params.put("menu.root.id", menuRootId);
params.put("position.id", user.getPosition().getId());
params.put("ORDERBY", " menu.root.id, menu.treeOrder asc ");
List<MenuResource> positionMenus = manager.query(PositionMenu.class, "menu", params, 0, -1);
menus.addAll(positionMenus);
}
}
httpRequest.setAttribute("menus", menus);
String currentMenuId = httpRequest.getParameter("m");
if (null != currentMenuId && !"".equals(currentMenuId.trim())) {
MenuResource m = (MenuResource) manager.read(MenuResource.class, Long.valueOf(currentMenuId));
httpRequest.setAttribute("m", m);
}
}
chain.doFilter(request, response);
}
3. ListAction基类实现
public String list(Class<T> target, Map params, DataScope scope, Pagination p) {
// 处理数据范围
if (null != scope) {
Passport currentUser = SystemUtils.getUser(true);
if (!SystemUtils.isExempt(currentUser)) {
currentUser = (Passport) authDao.load(Passport.class, currentUser.getId());
String alias = SystemUtils.getSimpleClassName(target).toLowerCase();
Long[] orgs = authDao.getOrganizationIds(currentUser, scope);
if (null != orgs && orgs.length > 0) {
params.put(alias + ".organization.id.IN", orgs);
} else {
try {
target.getDeclaredField("owner");
params.put(alias + ".owner.id", currentUser.getId());
} catch (NoSuchFieldException e) {
// no such field
resultList = new ArrayList();
return SUCCESS;
}
}
}
}
int count = domainManager.getCount(target, params);
p.setRecordCount(count);
resultList = domainManager.query(target, params, p.getFirstResult(), p.getMaxResult(), true);
return SUCCESS;
}
4. JSP标签库实现
public int doStartTag() throws JspException {
int returnValue = EVAL_BODY_INCLUDE;
Log log = LogUtil.getLog(FieldTag.class);
try {
JspWriter out = pageContext.getOut();
Passport user = SystemUtils.getUser(true);
String result = this.mask;
if (null != user) {
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(pageContext
.getServletContext());
AuthorityDAO authDao = wac.getBean(AuthorityDAO.class);
if (user.equals(owner) || authDao.hasFieldScope(user, Enum.valueOf(DataScope.class, scope), fieldName)) {
result = this.value;
} else {
returnValue = SKIP_BODY;
}
}
if (null != result && !"".equals(result.trim()))
out.print(result);
} catch (IOException ioe) {
log.error("Error in a tag <FieldTag>: ", ioe);
}
return (returnValue);
}