宅急送 项目第十天 权限管理
2017-04-24 10:35
281 查看
1. 权限管理功能 企业实现
第一种 使用开源权限控制框架第二种 自定义权限管理模块
1.1. 开源权限控制技术
Spring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配 置的Bean,充分利用了Spring IoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的 工作。
Apache shiro 开源免费的权限控制框架, Shiro 是一个用 Java 语言实现的框架,通过一个简单易用的 API 提供身份验证和授权。
使用 Shiro,您就能够为您的应用程序提供安全性而又无需从头编写所有代码。(与任何使用技术无关 , 用于任何JavaSE 和 JavaEE 项目中 )
1.2. 自定义权限控制模型
优点: 更加灵活,简化便于使用目标: 自定义通用权限控制模型,可以用于任何的项目
1.2.1. 权限模型数据模型
问题: 数据表设计 ?四个概念:
系统功能 (权限管理目的,就是为了对系统的功能访问进行控制 )
功能: 菜单项的功能 和 页面内部按钮功能
菜单功能
页面按钮功能
权限 ,具有访问某个功能的权限 ,权限和功能关系可以是 一对一 或者 多对多
角色 ,角色是权限的集合 ,为了方便用户授权 ,有了角色后,只需要将角色授予用户
用户 ,登陆系统的用户,具有角色,拥有系统功能权限
功能、权限、角色、用户 四者之间 都可以是多对多关系 —- 完整复杂权限模型有七张表 !
1.2.2. 今天系统的权限模型
用户表 user
功能权限表 auth_function
角色表 auth_role
角色-权限关系表 role_function
1.2.3. 自定义权限模型的权限控制原理
使用自定义注解+反射技术,基于代理 对业务方法访问 实现细粒度的权限控制提供权限管理系统数据:
权限控制原理分析: 基于 自定义注解+ 代理+ 反射 实现方法级别权限控制
2. 设计权限管理数据表
auth_function 表 (id , name, description )auth_role 表 (id , name , description )
user 表 添加 role_id 外键
问题: 实现动态菜单功能 ?
根据当前用户权限查询菜单项,生成菜单
需要将菜单一些数据 保存 auth_function 表
设计 Function 类 /** * 系统功能权限 * * @author seawind * */ public class Function { private String id; // uuid private String name; // 功能名称 private String description; // 功能描述 // 菜单项功能 private String generateMenu; // 是否生成菜单 private int zindex; // 菜单项优先级 private String page; // 点击菜单 跳转页面 private Function parentFunction;// 父功能点 private Set<Function> childrenFunctions = new HashSet<Function>(); // 子功能点 // 一个Function 属于很多 Role private Set<Role> roles = new HashSet<Role>(); }
配置Function.hbm.xml
<class name="cn.itcast.bos.domain.auth.Function" table="auth_function"> <id name="id"> <generator class="uuid"></generator> </id> <!-- 下面列要按照顺序 --> <property name="name" unique="true"></property> <property name="description"></property> <property name="page"></property> <property name="generateMenu"></property> <property name="zindex"></property> <!-- 表关联 --> <many-to-one name="parentFunction" class="cn.itcast.bos.domain.auth.Function" column="pid"></many-to-one> <set name="childrenFunctions"> <key column="pid"></key> <one-to-many class="cn.itcast.bos.domain.auth.Function"/> </set> <set name="roles" table="role_function"> <key column="function_id"></key> <many-to-many class="cn.itcast.bos.domain.auth.Role" column="role_id"></many-to-many> </set> </class>
设计 Role 类
/** * 系统角色 * * @author seawind * */ public class Role { private String id; // uuid private String name; // 角色名称 private String description; // 角色描述 // 关联Function private Set<Function> functions = new HashSet<Function>(); // 关联user private Set<User> users = new HashSet<User>(); }
编写Role.hbm.xml
<class name="cn.itcast.bos.domain.auth.Role" table="auth_role"> <id name="id"> <generator class="uuid"></generator> </id> <property name="name" unique="true"></property> <property name="description"></property> <!-- 关联 --> <set name="functions" table="role_function"> <key column="role_id"></key> <many-to-many class="cn.itcast.bos.domain.auth.Function" column="function_id"></many-to-many> </set> <set name="users"> <key column="role_id"></key> <one-to-many class="cn.itcast.bos.domain.user.User"/> </set> </class>
在User类 添加Role 关联
private Role role; public Role getRole() { return role; } public void setRole(Role role) { this.role = role; }
编写 User.hbm.xml 为 User关联Role
<!-- 关联角色 --> <many-to-one name="role" class="cn.itcast.bos.domain.auth.Role" column="role_id"></many-to-one>
3. 功能权限管理
3.1. 导入初始化数据
将auth_function.sql 初始化数据导入 auth_function表初始化数据参照 menu.json 菜单文件制作 !
3.2. 功能权限添加
点击功能权限管理— 添加权限 — /WEB-INF/pages/admin/function_add.jsp在添加权限页面中,获取父功能点列表,从列表中选择父功能点
<input name="parentFunction.id" class="easyui-combobox" data-options="valueField:'id',textField:'info', url:''${pageContext.request.contextPath}/function_ajaxlist.action"/>
编写 服务器代码 FunctionAction 查询所有列表显示
public class FunctionAction extends BaseAction implements ModelDriven<Function> { } public interface FunctionService { } public class FunctionServiceImpl extends BaseService implements FunctionService { }
将 DAO 注入 BaseService
将Service 注入BaseAction
<result name="ajaxlistSUCCESS" type="json"> <param name="root">functions</param> <param name="includeProperties"> \[\d+\]\.id, \[\d+\]\.info </param> </result>
在Function 添加 getInfo public String getInfo() {
return name + “(” + id + “)”;
}单提交
// 点击保存 $('#save').click(function(){ // 先判断form 是否通过校验,如果通过提交表单 if($("#functionForm").form('validate')){ $('#functionForm').submit(); }else{ $.messager.alert('警告','表单存在非法数据项!','warning'); } });
提交添加表单,在FunctionAction 添加save方法 ,保存权限信息
@Override public void saveFunction(Function function) { // 防止 "" 的id外键关联 if (function.getParentFunction() != null && function.getParentFunction().getId() != null && function.getParentFunction().getId().trim().length() == 0) { function.setParentFunction(null); } funtionDAO.save(function); }
3.3. 权限列表查询
为function.jsp的datagrid 添加url ,使用不分页的列表查询,服务器返回listurl : '${pageContext.request.contextPath}/function_list.action'
在服务器FunctionAction 添加 list 方法
<!-- 权限所有数据查询 --> <result name="listSUCCESS" type="json"> <param name="root">functions</param> <param name="includeProperties"> \[\d+\]\.id, \[\d+\]\.name, \[\d+\]\.description, \[\d+\]\.page, \[\d+\]\.generateMenu, \[\d+\]\.zindex </param> </result>
4. 角色管理(关联权限)
4.1. 添加角色(为角色授权)
4.1.1. 授权树的编写
/WEB-INF/pages/admin/role_add.jsp 添加角色页面制作ztree 勾选树,主要配置
var setting = { check : { enable : true } }
第一步: 在显示树位置
<ul class=”ztree” id=”functionTree” >
第二步: setting
第三步: zNodes 节点数据
第四步: 初始化树
$.fn.zTree.init($("#functionTree"), setting, zNodes);
显示授权树,根据auth_function 表查询,返回json数据
url : '${pageContext.request.contextPath}/function_treedata.action'
修改setting
var setting = { data : { key : { title : "t" }, simpleData : { enable : true, pIdKey: "parentId", } }, check : { enable : true, } };
服务器返回 ztree节点数据,应该包含 id、name、parentId !
在FunctionAction 添加treedata的方法
// 查询树,进行排序 DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Function.class); // 按照zindex 升序 detachedCriteria.addOrder(Order.asc("zindex"));
配置返回结果:
<!-- 授权树 --> <result name="treedataSUCCESS" type="json"> <param name="root">functions</param> <param name="includeProperties"> \[\d+\]\.id, \[\d+\]\.name, \[\d+\]\.parentId </param> </result>
在Function 实体类 添加getParentId 方法
public String getParentId() { if (parentFunction == null) { // 这是根节点 return "0"; } else { return parentFunction.getId(); } }
4.1.2. 提交添加角色表单
// 点击保存 $('#save').click(function(){ if($("#roleForm").form('validate')){ // 获得勾选ztree节点 var treeObj = $.fn.zTree.getZTreeObj("functionTree"); var nodes = treeObj.getCheckedNodes(true); // 将多个勾选id 转换为字符串,用, 分隔 var ids = []; for(var i=0;i<nodes.length; i++){ ids.push(nodes[i].id);// 将id 加入数组 } // 放入form 隐藏域 $('#functionIds').val(ids.join(",")); $("#roleForm").submit(); }else{ $.messager.alert('警告','表单存在非法数据项','warning'); } });
编写服务器 RoleAction
public class RoleAction extends BaseAction implements ModelDriven<Role> {} public interface RoleService { } public class RoleServiceImpl extends BaseService implements RoleService { }
将DAO 注入 BaseService
将Service 注入BaseAction
添加角色业务代码
@Override public void saveRole(Role role, String functionIds) { // 将role信息保存角色表 roleDAO.save(role); // 持久态 // 建立 role 和 function联系,向role_function 中间表插入数据 if (functionIds != null) { String[] ids = functionIds.split(","); for (String id : ids) { Function function = funtionDAO.findById(id); // 功能权限 role.getFunctions().add(function); // 多对多关联,向中间表插入数据 } } }
配置跳转
<!-- 角色管理 --> <action name="role_*" class="roleAction" method="{1}"> <result name="saveSUCCESS">/WEB-INF/pages/admin/role.jsp</result> </action>
4.2. 角色列表查询
/WEB-INF/pages/admin/role.jsp 修改 datagrid 的urlurl : '${pageContext.request.contextPath}/role_list.action',
在服务器 RoleAction 添加 list 方法
<!-- 角色列表--> <result name="listSUCCESS" type="json"> <param name="root">roles</param> <param name="includeProperties"> \[\d+\]\.id, \[\d+\]\.name, \[\d+\]\.description </param> </result>
5. 用户管理(关联角色)
已经在第二天, 创建 UserAction 完成修改密码5.1. 添加用户
/** * 用户 User entity. @author MyEclipse Persistence Tools */
public class User implements java.io.Serializable { // Fields private String id; // 编号 修改生成策略 uuid private String username; // 用户名 private String password; // 密码 private Double salary; // 工资 private Date birthday; // 生日 private String gender; // 性别 private String station; // 单位 private String telephone; // 电话 private String remark; // 电话 }
修改 /WEB-INF/pages/admin/userinfo.jsp
<tr> <td>为用户授予角色</td> <td colspan="3"> <input name="role.id" class="easyui-combobox" data-options="valueField:'id',textField:'name',url:'${pageContext.request.contextPath }/role_list.action'" /> </td> </tr>
直接使用在RoleAction 的 list 方法
点击 保存按钮提交form表单
$('#save').click(function(){ // 先校验form if($('#userForm').form('validate')){ $('#userForm').submit(); }else{ $.messager.alert('警告','表单存在非法数据项','warning'); } });
在 UserAction 添加 save 方法
public void saveUser(User user) { // 防止 Role的id 为空串 if (user.getRole() != null && user.getRole().getId() != null && user.getRole().getId().trim().length() == 0) { user.setRole(null); } // 对密码 进行 md5 加密 user.setPassword(MD5Utils.md5(user.getPassword())); userDAO.save(user); }
配置结果跳转
<!-- 添加用户 --> <result name="saveSUCCESS"> /WEB-INF/pages/admin/userlist.jsp </result>
如果添加的中文账户,在登陆页面无法登陆 !
无法输入中文
5.2. 用户列表查询
/WEB-INF/pages/admin/userlist.jsp修改 datagrid 的 url
url : "${pageContext.request.contextPath}/user_list.action",
在服务器 UserAction 提供 list 查询方法
<!-- 查询所有用户列表 --> <result name="listSUCCESS" type="json"> <param name="root">users</param> <param name="includeProperties"> \[\d+\]\.id, \[\d+\]\.username, \[\d+\]\.telephone, \[\d+\]\.gender, \[\d+\]\.station, \[\d+\]\.salary, \[\d+\]\.birthday, \[\d+\]\.role\.name </param> </result>
修改 userlist.jsp 定义 datagrid 的 columns
添加 一列 显示角色
{ field : 'role', title :'角色', width : 400 , align : 'left', rowspan : 2, formatter : function(value,rowData,rowIndex){ if(value == null){ return ""; }else{ return value.name; } } }
5.3. 为用户授予(修改)角色
1、 为用户列表 每行数据,添加右键效果原始js 事件 : oncontextmenu 事件 (鼠标右键事件)
使用 datagrid 数据行 右键事件
阻止默认右键菜单显示 :
e.preventDefault(); // 阻止默认事件
2、 自定义右键菜单
使用 easyui 提供 menu 控件 制作菜单
<!-- 自定义菜单 --> <div id="mm" class="easyui-menu" style="width:120px;"> <div onclick="alert('点击我了!');">菜单一</div> <div>菜单二</div> </div>
点击右键时,在鼠标位置弹出菜单
// 弹出自定义菜单 $('#mm').menu('show', { left: e.pageX, top: e.pageY });
复制 “授予角色.txt”window代码 进行授权
业务代码
@Override public void grantRole(User user) { User exist = userDAO.findById(user.getId()); exist.setRole(user.getRole()); // 关联角色 自动更新 }
6. 使用代理进行权限拦截
基于 自定义注解 + 反射 + 代理 实现方法级别细粒度权限控制URL粗粒度权限控制, 比如admin登陆,访问 /admin/* 开始页面
6.1. 自定义注解
/** * 自定义注解 * * @author seawind * */ @Retention(RetentionPolicy.RUNTIME) // 运行时使用 @Target(value = { ElementType.METHOD }) // 修改方法 @Inherited // 使用注解应用具有继承性 public @interface Privilege { String value(); // 这个属性代表访问业务方法 需要权限 }
在需要进行权限控制业务方法上,使用该注解
// 业务方法 --- 添加用户 @Privilege("添加用户") public String save() { // 调用业务层 保存用户 userService.saveUser(user); return "saveSUCCESS"; }
6.2. 为目标业务方法建立代理
为目标创建对象创建代理 : JDK动态代理、Cglib动态代理、 静态代理对于Struts2 Action 而言,Interceptor 就是代理
public class PrivilegeInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { return null; } }
6.3. 在代理中通过反射解析注解信息,控制权限
public class PrivilegeInterceptor extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { // 假设 用户已经登陆 // 1、 判断目标Action业务方法上,是否具有Privilege 注解 Class c = invocation.getAction().getClass(); // 目标Action的Class对象 String methodName = invocation.getProxy().getMethod(); // 目标业务方法名称 Method method = c.getDeclaredMethod(methodName); // 判断是否具有注解 if (method.isAnnotationPresent(Privilege.class)) { // 有注解 ,需要权限 // 2、 获得注解中需要权限 Privilege privilege = method.getAnnotation(Privilege.class); String needPrivilege = privilege.value(); // 3、判断当前用户是否具有该权限 User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user"); if (PrivilegeUtils.checkHasPrivilege(user, needPrivilege)) { // 有权限 return invocation.invoke(); } else { // 没有权限 return "noprivilege"; } } else { // 无注解,不需要权限 return invocation.invoke(); } } }
抽取 PrivilegeUtils 工具类
/** * 判断 用户是否具有 访问权限 * * @param user * @param needPrivilege * @return */ public static boolean checkHasPrivilege(User user, String needPrivilege) { // admin 直接放行 if (user.getUsername().equals("admin")) { return true; } // 正常判断流程 Role role = user.getRole(); if (role == null) { // 当前登录用户没角色,没权限 return false; } else { // 有角色 Set<Function> functions = role.getFunctions(); for (Function function : functions) { if (function.getName().equals(needPrivilege)) { // 满足权限 return true; } } return false; } }
在 struts.xml 配置 权限拦截器
<!-- 注册拦截器 --> <interceptors> <interceptor name="login" class="cn.itcast.bos.web.interceptor.LoginInterceptor"></interceptor> <interceptor name="privilege" class="cn.itcast.bos.web.interceptor.PrivilegeInterceptor"></interceptor> <!-- 定义新的拦截器栈 --> <interceptor-stack name="loginStack"> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="login"></interceptor-ref> </interceptor-stack> <interceptor-stack name="privilegeStack"> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="login"></interceptor-ref> <interceptor-ref name="privilege"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 设置默认拦截器栈 --> <default-interceptor-ref name="privilegeStack"></default-interceptor-ref>
定义全局结果集 noprivilege 页面
<!-- 配置全局结果集 --> <global-results> <result name="login">/login.jsp</result> <result name="noprivilege">/noprivilege.jsp</result> </global-results>
问题: 控制权限 出现 No Session 异常?
原因,参考图四
解决: 在登陆业务方法中 UserServiceImpl 的 login 方法
public User login(User user) { List<User> list = userDAO.findByNamedQuery("User.login", user.getUsername(), MD5Utils.md5(user.getPassword())); User loginUser = list.isEmpty() ? null : list.get(0); if (loginUser.getRole() != null) { // 手动对用户管理 权限信息初始化 Hibernate.initialize(loginUser.getRole().getFunctions()); } return loginUser; }
7. 在应用权限管理后,控制页面显示
7.1. 动态菜单
根据当前用户具有权限,来显示菜单修改 /WEB-INF/pages/common/index.jsp
在动态返回 json 菜单数据时, ztree使用 SimpleData ,存在id 和 pId , 如果使用pId 返回会有问题, 配置ztree setting ,将返回pId 定制为 parentId
var setting = { data : { key : { title : "t" // 鼠标在停留在菜单上提示 }, simpleData : { // 简单数据 enable : true, pIdKey: "parentId", } }, callback : { onClick : onClick } };
将页面 加载menu.json 的url 改为动态地址
url : '${pageContext.request.contextPath}/function_menu.action',
在FunctionAction 提供 menu 方法,返回当前用户具有权限对应菜单
public String menu() { // 复杂查询 使用 QBC DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Function.class); // 查询当前用户具有权限菜单 User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user"); if (!user.getUsername().equals("admin")) {// 如果用户是admin 需要显示所有功能 // 多表关联,每关联一个表,创建一个别名 detachedCriteria.createAlias("roles", "r"); detachedCriteria.createAlias("r.users", "u"); detachedCriteria.add(Restrictions.eq("u.id", user.getId())); } // 查询generateMenu 为 1 的功能 detachedCriteria.add(Restrictions.eq("generateMenu", "1")); // 按照zindex 排序 detachedCriteria.addOrder(Order.asc("zindex")); // 调用业务层 List<Function> functions = functionService.findTreeData(detachedCriteria); // 压入值栈 ActionContext.getContext().put("functions", functions); return "menuSUCCESS"; }
配置 struts-auth.xml 返回结果
<!-- 动态菜单 --> <result name="menuSUCCESS" type="json"> <param name="root">functions</param> <param name="includeProperties"> \[\d+\]\.id, \[\d+\]\.name, \[\d+\]\.parentId, \[\d+\]\.click, \[\d+\]\.page </param> </result>
没有click属性 ,在Function添加 getClick 方法
public boolean getClick() { if (page == null || page.trim().length() == 0) { // 没有page属性 return false; } else { return true; } }
7.2. 页面内部动态功能按钮
实现细粒度显示控制 ,控制到页面内部每一个按钮的显示使用 自定义标签来实现
步骤一: 自定义标签类
使用简单标签库
/** * 自定义标签类 * * @author seawind * */ public class PrivilegeTag extends SimpleTagSupport { // 接收属性 private String value; // 显示内容需要的权限 public void setValue(String value) { this.value = value; } @Override public void doTag() throws JspException, IOException { User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user"); if (PrivilegeUtils.checkHasPrivilege(user, value)) { // 具有权限, 显示标签体内容 this.getJspBody().invoke(null); // 等价 // this.getJspBody().invoke(this.getJspContext().getOut()); } } }
步骤二: 配置标签描述文件 tld
修改头信息 , 没有提示
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"> <tag> <name>privilege</name> <tag-class>cn.itcast.bos.web.tag.PrivilegeTag</tag-class> <body-content>scriptless</body-content> <!-- 定义属性 --> <attribute> <name>value</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <!-- 接收EL --> </attribute> </tag>
步骤三: 在jsp导入标签库
<%@taglib uri="http://www.itcast.cn/tag" prefix="itcast"%> <itcast:privilege value="收派标准"> { id : 'button-add', text : '增加', iconCls : 'icon-add', handler : doAdd }, </itcast:privilege>
其它
课前资料
课后资料
权限数据模型分析
自定义权限模型原理分析
控制权限发生noSession
需要功能动态生成菜单分析
课程视频内容
相关文章推荐
- 宅急送项目的第七天笔记!(JBPM工作流和介绍 -- 权限管理模型)
- 宅急送项目的第九天笔记!( 角色--权限管理)
- 中小型项目的权限管理的数据关系图
- 一个管理TFS权限的工具,同时管理项目sharepoint和report
- 软件项目最佳实践: 又谈权限管理
- linux项目权限管理及LVM习题
- Window下多项目中SVN权限管理精辟解析
- 一个管理TFS权限的工具,同时管理项目/sharepoint/report
- 单点登录cas与权限管理框架shiro集成------普通web项目方式
- Jenkins配置基于角色的项目权限管理
- centos 6.4 SVN服务器多个项目的权限分组管理
- ASP.NET通用权限管理系统(FrameWork) 0.9.0 Beta (开源项目)
- Asp.Net大型项目实践(10)-基于MVC Action粒度的权限管理(在线demo,全部源码)
- 软件项目管理系统:项目资料模块权限分配设计文档
- 单点登录cas与权限管理框架shiro集成------spring项目方式
- 中小型项目的权限管理的数据关系图
- 多项目SVN权限管理
- 项目管理之权限管理
- 统一项目管理平台(UMPlatForm.NET) 4.9 操作权限管理
- svn对项目权限进行管理