权限管理 订阅
权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。 很多人常将“用户身份认证”、“密码加密”、“系统管理”等概念与权限管理概念混淆。 展开全文
权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。 很多人常将“用户身份认证”、“密码加密”、“系统管理”等概念与权限管理概念混淆。
信息
规    则
根据系统设置的安全规则
认    证
用户和密码的系统
分    类
身份认证”、“密码加密”
中文名
权限管理
外文名
authority management
权限管理场景举例
企业IT管理员一般都能为系统定义角色,给用户分配角色。这就是最常见的基于角色访问控制。场景举例:1、给张三赋予“人力资源经理”角色,“人力资源经理”具有“查询员工”、“添加员工”、“修改员工”和“删除员工”权限。此时张三能够进入系统,则可以进行这些操作;2、去掉李四的“人力资源经理”角色,此时李四就不能够进入系统进行这些操作了。以上举例,局限于功能访问权限。还有一些更加丰富、更加细腻的权限管理。比如:1、因为张三是北京分公司的“人力资源经理”,所以他能够也只能够管理北京分公司员工和北京分公司下属的子公司(海淀子公司、朝阳子公司、西城子公司、东城子公司等)的员工;2、因为王五是海淀子公司的“人力资源经理”,所以他能够也只能够管理海淀子公司的员工;3、普通审查员审查财务数据的权限是:在零售行业审核最高限额是¥50万,在钢铁行业最高限额是¥1000万;高级审查员不受该限额限制;4、ATM取款每次取款额不能超过¥5000元,每天取款总额不能超过¥20000元。这些权限管理和数据(可以统称为资源)直接相关,又称为数据级权限管理、细粒度权限管理或者内容权限管理。
收起全文
精华内容
下载资源
问答
  • java web简单权限管理设计

    万次阅读 多人点赞 2015-03-19 23:23:05
    推荐最新技术springboot版权限管理(java后台通用权限管理系统(springboot)),采用最新技术架构,功能强大! 注:由于该项目比较老,所以没有采用maven管理,建议下载springboot权限管理系统,对学习和使用会更有...

    源码免费下载地址:关注微信公众号“虾米聊吧”,回复关键字“权限

     

    推荐最新技术springboot版权限管理java后台通用权限管理系统(springboot)),采用最新技术架构,功能强大

    注:由于该项目比较老,所以没有采用maven管理,建议下载springboot权限管理系统,对学习和使用会更有帮助。

    springboot权限管理系统介绍地址:https://blog.csdn.net/zwx19921215/article/details/97806078

    springboot权限管理系统初级版下载地址:http://zyshare.cn/resource/detail/3

    springboot权限管理系统高级版下载地址:http://zyshare.cn/resource/detail/7

    springboot个人博客系统:https://blog.csdn.net/zwx19921215/article/details/102665020

    后台管理系统模板html打包下载: http://zyshare.cn/resource/detail/14

     

    最近在做一个网站类型项目,主要负责后台,ui框架选型为jquery easy ui,项目架构为spring mvc + spring jdbc,简单易用好上手!搭建好框架后开始了第一个任务,设计并实现一套简单的权限管理功能。

    一套最基本的权限管理包括用户、角色、资源。

     

    数据库设计

    我的设计如下:

    用户:user

    角色:role

    用户-角色:user_role

    资源:resource(包括上级菜单、子菜单、按钮等资源)

    角色-资源:role_resource

    标准的权限管理系统设计为以上5张表。

     

    注:用户、用户-角色我就不做说明了,这两个是很简单的两块,用户的crud,以及为用户分配角色(多对多的关系)稍微琢磨一下就清楚了,下面都是针对为角色分配权限的实现

    效果图:

    项目结构

     

    后台实现

    展示层采用ztree树

     

    role.jsp

     

    <%@ page contentType="text/html;charset=UTF-8"%>
    <%@ include file="/views/back/include/taglib.jsp"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta name="decorator" content="back" />
    <script type="text/javaScript">
    //打开菜单窗口
    function openMenuDialog(){
    	var selected = $("#list").datagrid('getSelected');
        if (selected != null) {
        	$("#id").val(selected.id);
        	queryMenus(selected.id);
        	$("#menuWindow").window("open");
        } else {
       	 $.messager.alert('提示', "未选择数据!"); 
        }
    }
    //角色-菜单信息入库
    function ajaxSubmit(rid,idstr){
    	$.post("${ctx}/roleMenu/save.jhtml",{"roleId":rid,"ids":idstr},function(obj){
    		$.messager.alert('提示',obj.msg);
    		$("#menuWindow").window('close');
    	},'json');
    }
    </script>
    <!-- ztree -->
    <script type="text/javascript">
    var tree = "";
    var setting = {
    	check : {
    		chkboxType:{"Y":"ps","N":"s"},//勾选checkbox对于父子节点的关联关系,取消勾选时不关联父
    		chkStyle:"checkbox",
    		enable : true	//是否复选框
    	},
    	//数据
    	data : {
    		simpleData : {
    			enable : true
    		}
    	}
    };
    //查询菜单信息
    function queryMenus(roleId){
    	$.post('${ctx}/role/treedata.jhtml', {'roleId':roleId}, function(zNodes) {
    		for (var i = 0; i < zNodes.length; i++) {
    			if (zNodes[i].isParent) {
    
    			} else {
    				//zNodes[i].icon = "${ctxStatic}/images/532.ico";//设置图标
    			}
    		}
    		tree = $.fn.zTree.init($("#tree"), setting, zNodes);
    		tree.expandAll(true);//全部展开
    		//var nodes = treeObj.getNodes();
    	}, 'json');
    }
    
    //获取选中节点
    function onCheck(){
    	 var rid = $("#id").val();
    	 var treeObj=$.fn.zTree.getZTreeObj("tree");
         var nodes=treeObj.getCheckedNodes(true);
         var ids = new Array();
         for(var i=0;i<nodes.length;i++){
        	//获取选中节点的值
        	 ids.push(nodes[i].id);
    	    // v+=nodes[i].id + ",";
    	    //alert(nodes[i].id); 
         }
    	ajaxSubmit(rid,ids);     
    }
    </script>
    </head>
    <body>
    	<!-- 数据表格 -->
    	<table id="list" url='${ctx}/role/list/page.jhtml' method='post'
    		class="easyui-datagrid" style="width:100%;" fitcolumns="true" 
    		toolbar='#tb' pagination='true' rownumbers='true' singleSelect='true'>
    		<thead>
    			<tr>
    				<th field='name' sortable='true' width='100'>角色名称</th>
    				<th field='description' width='200' align='right'>描述</th>
    				<th field='createTimeFormat' width='150' align='center'>创建时间</th>				
    			</tr>
    		</thead>
    	</table>
    	
    	<!-- 编辑栏  -->
    	<div id="tb" style="padding:5px 5px;">
    		<div>
    			<p2p:permission module="role" code="add"><a href="#" class="easyui-linkbutton" iconCls="icon-add" onclick="openCreateDialog();">新增</a></p2p:permission>
    			<p2p:permission module="role" code="edit"><a href="#" class="easyui-linkbutton" iconCls="icon-edit" onclick="openUpdateDialog();">编辑</a></p2p:permission>
    			<p2p:permission module="role" code="delete"><a href="#" class="easyui-linkbutton" iconCls="icon-remove" onclick="del();">删除</a></p2p:permission>
    			<p2p:permission module="role" code="authority"><a href="#" class="easyui-linkbutton" iconCls="icon-edit" onclick="openMenuDialog();">设置权限</a></p2p:permission>
    		</div>
    		<!-- 搜索项 -->
    		<div style="margin-top:5px;padding-left:5px">
    			用户名:   <input id="query_name" class="easyui-textbox" type="text" style="width:110px" />
    			创建日期: <input id="query_startDate" class="easyui-datebox" style="width:110px">
    			至: 	   <input id="query_endDate" class="easyui-datebox" style="width:110px">
    			<a onclick="reload();" href="#" class="easyui-linkbutton" iconCls="icon-search">查询</a>
    		</div>
    	</div>
    	
    	
    	
    	<!-- 权限窗口 -->
    	<div id="menuWindow" class="easyui-window" title="配置权限" data-options="modal:true,iconCls:'icon-save',footer:'#menuWindowfooter'" style="width:350px;height:420px;padding:10px">
    		<div id="tree" class="ztree" style="padding: 10px 20px;"></div>
    	</div>
    	<div id="menuWindowfooter" style="padding:5px;text-align:right;"> 
    		<a href="#" onclick="onCheck();" class="easyui-linkbutton" data-options="iconCls:'icon-save'">提交</a>
    	</div>
    	
    </body>
    </html>
    

     

     

    action层
    RoleAction.java

     

     

     

    @RequestMapping(value = "/treedata.jhtml")
    	@ResponseBody
    	public String treedata(HttpServletRequest request, Model model) {
    		DynamicParams params = new DynamicParams(request);
    		List<Map<String, Object>> mapList = Lists.newArrayList();
    
    		params.put("allMenu", "allMenu");
    		List<Menu> list = authManager.findMenuList(params);
    
    		List<RoleMenu> roleMenus = authManager.findRoleMenuList(params);
    
    		for (int i = 0; i < list.size(); i++) {
    			Menu e = list.get(i);
    			Map<String, Object> map = Maps.newHashMap();
    			map.put("id", e.getId());
    			map.put("pId", e.getParentId() != null ? e.getParentId() : 0);
    			map.put("name", e.getName());
    			for (RoleMenu roleMenu : roleMenus) {
    				if (roleMenu.getMenuId() == e.getId()) {
    					map.put("checked", true);
    				}
    			}
    			mapList.add(map);
    		}
    
    		return toJson(mapList);
    	}

     

     

     

     

     

    service层

    AuthManager.java

     

    // 菜单管理
    
    	public List<Menu> findMenuList(DynamicParams params) {
    		List<Menu> menus = new ArrayList<Menu>();
    
    		if ("allMenu".equals(params.getString("allMenu"))) {
    			menus = menuDao.findList(params);
    		} else {
    			// 通过用户查询角色
    			List<UserRole> userRoles = userRoleDao.findList(params);
    			// 通过角色查询菜单
    			List<RoleMenu> roleMenus = new ArrayList<RoleMenu>();
    			if (userRoles != null && userRoles.size() > 0) {
    				for (UserRole userRole : userRoles) {
    					params = new DynamicParams();
    					if (userRole != null) {
    						if (userRole.getRoleId().equals(params.getString("rid"))) {
    							break;
    						}
    						params.put("roleId", userRole.getRoleId().toString());
    						List<RoleMenu> rms = roleMenuDao.findList(params);
    						for (RoleMenu roleMenu : rms) {
    							roleMenus.add(roleMenu);
    						}
    					}
    				}
    			}
    
    			// 查询菜单信息
    			for (RoleMenu roleMenu : roleMenus) {
    				if (roleMenu != null) {
    					Menu menu = menuDao.find(roleMenu.getMenuId());
    					if (menu != null) {
    						menus.add(menu);
    					}
    				}
    			}
    			menus = removeDuplicate(menus);
    			Collections.sort(menus);
    		}
    		return menus;
    	}
    /**
    	 * 去除菜单中重复项
    	 * 
    	 * @param list
    	 * @return
    	 */
    	private List<Menu> removeDuplicate(List<Menu> list) {
    		List<Menu> result = new ArrayList<Menu>();
    		Set<Long> menuIds = new HashSet<Long>();
    		for (int i = 0; i < list.size(); i++) {
    			Menu m = list.get(i);
    			if (m != null && menuIds.add(m.getId())) {
    				result.add(m);
    			}
    		}
    		return result;
    	}

     

    public List<RoleMenu> findRoleMenuList(DynamicParams params) {
    		List<RoleMenu> roleMenus = roleMenuDao.findList(params);
    		return roleMenus;
    	}

     

     

    Dao层

    menuDao

    @Override
    	protected void createQuery(DynamicParams params, StringBuffer sql, List<Object> args) {
    		sql.append("select s.* from sys_menu s where 1=1 ");
    
    		String parentId = params.getString("parentId");
    		if (StringUtils.isNotBlank(parentId)) {
    			sql.append(" and parent_id = ? ");
    			args.add(parentId);
    		}
    
    		String sort = params.getString("sort");
    		String order = params.getString("order");
    
    		if (StringUtils.isNotBlank(sort)) {
    			sql.append(" order by ").append(hump2underline(sort));
    			if (StringUtils.isNotBlank(order)) {
    				sql.append(" " + order);
    			} else {
    				sql.append(" desc ");
    			}
    		} else {
    			sql.append("order by sort asc,id desc ");
    		}
    	}


    userRoleDao

     

    @Override
    	protected void createQuery(DynamicParams params, StringBuffer sql, List<Object> args) {
    		sql.append("select s.* from sys_user_role s where 1=1 ");
    		Long adminId = params.getLong("adminId");
    		if (adminId != null) {
    			sql.append(" and s.user_id = ?");
    			args.add(adminId);
    		}
    	}


    roleMenuDao

     

     

    @Override
    	protected void createQuery(DynamicParams params, StringBuffer sql, List<Object> args) {
    		sql.append("select s.* from ").append("sys_role_menu").append(" s where 1=1 ");
    		Long adminId = params.getLong("roleId");
    		if (adminId != null) {
    			sql.append(" and s.role_id = ?");
    			args.add(adminId);
    		}
    	}

     

     

     

    在WEB-INF目录下建立文件夹tlds 建立自定义标签文件shiros.tld,我们通过自定义标签实现页面按钮的控制。

     

     

    <span style="color:#333333;"><?xml version="1.0" encoding="UTF-8" ?>
    <taglib 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"
    	version="2.0">
    	<description>p2p permission taglib</description>
    	<display-name>permission taglib</display-name>
    	<tlib-version>1.0</tlib-version>
    	<short-name>p2p_back</short-name>
    	<uri>http://vanfon.p2p.cn/</uri>
    
    	<tag>
    		<description>权限校验标签,有权限就显示标签体的内容,否则不显示</description>
    		<name>permission</name>
    		<tag-class>com.vanfon.p2p.back.tag.PermissionTag</tag-class>
    		<body-content>JSP</body-content>
    		<attribute>
    			<description></description>
    			<name>module</name>
    			<required>true</required>
    			<rtexprvalue>false</rtexprvalue>
    		</attribute>
    		<attribute>
    			<description></description>
    			<name>code</name>
    			<required>true</required>
    			<rtexprvalue>false</rtexprvalue>
    		</attribute>
    	</tag>
    </taglib></span>

     

     

    自定义标签类

     

    package com.vanfon.p2p.back.tag;
    
    import java.util.List;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.jsp.JspException;
    import javax.servlet.jsp.tagext.TagSupport;
    
    import com.vanfon.p2p.entity.system.Admin;
    import com.vanfon.p2p.entity.system.Menu;
    import com.vanfon.p2p.manager.system.AuthManager;
    import com.vanfon.p2p.utils.DynamicParams;
    import com.vanfon.p2p.utils.SpringContextHolder;
    
    /**
     * 权限控制标签
     * 
     * @author zhangwx
     * @date 2015-2-5
     */
    public class PermissionTag extends TagSupport {
    
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 4592227792811389132L;
    
    	private String module;// 属性名必须与JSP自定义标签的属性名一样
    
    	private String code;
    
    	public String getModule() {
    		return module;
    	}
    
    	public void setModule(String module) {
    		this.module = module;
    	}
    
    	public String getCode() {
    		return code;
    	}
    
    	public void setCode(String code) {
    		this.code = code;
    	}
    
    	@Override
    	public int doStartTag() throws JspException {
    		boolean result = false;
    		HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest();// 通过成员变量获取HttpServletRequest对象
    		Admin admin = (Admin) request.getSession().getAttribute("admin");// 获取登录到系统的用户
    		if (admin != null) {
    			if ("1".equals(String.valueOf(admin.getIfsuper()))) {// 超级管理员
    				result = true;
    			} else {
    				DynamicParams params = new DynamicParams();
    				params.put("id", String.valueOf(admin.getId()));
    				params.put("module", this.module);
    				params.put("code", this.code);
    				AuthManager authManager = SpringContextHolder.getBean(AuthManager.class);
    				List<Menu> userRoleAuths = authManager.findUserRoleAuthList(params);
    				if (userRoleAuths != null && userRoleAuths.size() > 0) {
    					result = true;
    				}
    			}
    		}
    		return result ? EVAL_BODY_INCLUDE : SKIP_BODY;
    	}
    }

     

     

     

     

     

     

     

     

    以上就是该权限管理中权限树(为角色分配权限)的大体实现。

    项目源码下载地址:http://www.zyshare.cn/resource/detail/1

    注:由于本项目年代久远,所以技术比较老旧,新人学习建议此项目 java后台通用权限管理系统(springboot)

    推荐项目:java后台通用权限管理系统(springboot)

    博主qq:193459197  , qq群技术交流与支持:557911445

     

    关注微信公众号“虾米聊吧”,回复“权限”获取源码,后续持续放送技术架构和资料干货!!!  

     

    一个热衷于分享技术和生活的程序猿,让我们一起交流吧~      
                        
                      微信扫描二维码,关注我的公众号


     
     

    展开全文
  • 权限管理

    千次阅读 2017-12-29 20:24:07
    关于权限菜单的设计 ... 1.... 权限管理往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断“Who对What(Which)进行How的操作”的逻辑表达式是否为真。针对不同的应用,需要

    关于权限菜单的设计


    http://blog.csdn.net/bearyb1982/article/details/2448301


    权限设计(初稿)   
      1. 前言:   
      权限管理往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断“Who对What(Which)进行How的操作”的逻辑表达式是否为真。针对不同的应用,需要根据项目的实际情况和具体架构,在维护性、灵活性、完整性等N多个方案之间比较权衡,选择符合的方案。   
      2. 目标:   
      直观,因为系统最终会由最终用户来维护,权限分配的直观和容易理解,显得比较重要简单,包括概念数量上的简单和意义上的简单还有功能上的简单。想用一个权限系统解决所有的权限问题是不现实的。设计中将常常变化的“定制”特点比较强的部分判断为业务逻辑,而将常常相同的“通用”特点比较强的部分判断为权限逻辑就是基于这样的思路。   
      3. 现状:   
      对于在企业环境中的访问控制方法,一般有三种:   
      1.自主型访问控制方法:目前在我国的大多数的信息系统中的访问控制模块中基本是借助于自主型访问控制方法中的访问控制列表(ACLs)。   
      2.强制型访问控制方法:用于多层次安全级别的军事应用。   
      3.基于角色的访问控制方法(RBAC):是目前公认的解决大型企业的统一资源访问控制的有效方法。其显著的两大特征是:1.减小授权管理的复杂性,降低管理开销。2.灵活地支持企业的安全策略,并对企业的变化有很大的伸缩性。   
      4. 名词:   
      粗粒度:表示类别级,即仅考虑对象的类别(the   type   of   object),不考虑对象的某个特定实例。比如,用户管理中,创建、删除,对所有的用户都一视同仁,并不区分操作的具体对象实例。   
      细粒度:表示实例级,即需要考虑具体对象的实例(the   instance   of   object),当然,细粒度是在考虑粗粒度的对象类别之后才再考虑特定实例。比如,合同管理中,列表、删除,需要区分该合同实例是否为当前用户所创建。   
      5. 原则:   
      权限逻辑配合业务逻辑。即权限系统以为业务逻辑提供服务为目标。相当多细粒度的权限问题因其极其独特而不具通用意义,它们也能被理解为是“业务逻辑”的一部分。比如,要求:“合同资源只能被它的创建者删除,与创建者同组的用户可以修改,所有的用户能够浏览”。这既可以认为是一个细粒度的权限问题,也可以认为是一个业务逻辑问题。在这里它是业务逻辑问题,在整个权限系统的架构设计之中不予过多考虑。当然,权限系统的架构也必须要能支持这样的控制判断。或者说,系统提供足够多但不是完全的控制能力。即,设计原则归结为:“系统只提供粗粒度的权限,细粒度的权限被认为是业务逻辑的职责”。   
      权限公式:Who+What(Which)+How   的问题,在这里我们实现What和部分Which的权限问题(粗粒度和细粒度相合,到一定的程度),其他的权限问题留给业务逻辑解决   
      6. 概念:   
      Who:权限的拥用者或主体(Principal(负责人)、User、Group、Role、Actor等等)   
      What:权限针对的对象或资源(Resource、Class)。   
      How:具体的权限(Privilege,   正向授权与负向授权)。   
      Role:是角色,拥有一定数量的权限。   
      Operator:操作。表明对What的How   操作。   
      7. 解释:   
      User:与   Role   相关,用户仅仅是纯粹的用户,权限是被分离出去了的。User是不能与   Privilege   直接相关的,User   要拥有对某种资源的权限,必须通过Role去关联。解决   Who   的问题。   
      Resource:就是系统的资源,比如部门新闻,文档等各种可以被提供给用户访问的对象。   
      Privilege:是Resource   Related的权限。就是指,这个权限是绑定在特定的资源实例上的。比如说部门新闻的发布权限,叫做"部门新闻发布权限"。这就表明,该Privilege是一个发布权限,而且是针对部门新闻这种资源的一种发布权限。Privilege是由Creator在做开发时就确定的。Privilege   如"删除"   是一个抽象的名词,当它不与任何具体的   Object   或   Resource   绑定在一起时是没有任何意义的。拿新闻发布来说,发布是一种权限,但是只说发布它是毫无意义的。因为不知道发布可以操作的对象是什么。只有当发布与新闻结合在一起时,才会产生真正的   Privilege。这就是   Privilege   Instance。   
      Role:是粗粒度和细粒度(业务逻辑)的接口,一个基于粗粒度控制的权限框架软件,对外的接口应该是Role,具体业务实现可以直接继承或拓展丰富Role的内容,Role不是如同User或Group的具体实体,它是接口概念,抽象的通称。   
      Operator的定义包括了Resource   Type和Method概念。即,What和How的概念。之所以将What和How绑定在一起作为一个Operator概念而不是分开建模再建立关联,这是因为很多的How对于某What才有意义。比如,发布操作对新闻对象才有意义,对用户对象则没有意义。   
      8. 思想:   
      权限系统的核心由以下三部分构成:1.创造权限,2.分配权限,3.使用权限,然后,系统各部分的主要参与者对照如下:1.创造权限   -   Creator创造,2.分配权限   -   Administrator   分配,3.使用权限   -   User:   
      1.   Creator   创造   Privilege,   Creator   在设计和实现系统时会划分,一个子系统或称为模块,应该有哪些权限。这里完成的是   Privilege   与   Resource   的对象声明,并没有真正将   Privilege   与具体Resource   实例联系在一起,形成Operator。   
      2.   Administrator   指定   Privilege   与   Resource   Instance   的关联。在这一步,   权限真正与资源实例联系到了一起,   产生了Operator(Privilege   Instance)。Administrator利用Operator这个基本元素,来创造他理想中的权限模型。如,创建角色,创建用户组,给用户组分配用户,将用户组与角色关联等等...这些操作都是由   Administrator   来完成的。   
      3.   User   使用   Administrator   分配给的权限去使用各个子系统。Administrator   是用户,在他的心目中有一个比较适合他管理和维护的权限模型。于是,程序员只要回答一个问题,就是什么权限可以访问什么资源,也就是前面说的   Operator。程序员提供   Operator   就意味着给系统穿上了盔甲。Administrator   就可以按照他的意愿来建立他所希望的权限框架可以自行增加,删除,管理Resource和Privilege之间关系。可以自行设定用户User和角色Role的对应关系。(如果将   Creator看作是   Basic   的发明者,   Administrator   就是   Basic   的使用者,他可以做一些脚本式的编程)   Operator是这个系统中最关键的部分,它是一个纽带,一个系在Programmer,Administrator,User之间的纽带。

    但凡涉及多用户不同权限的网络或者单机程序,都会有权限管理的问题,比较突出的是MIS系统。     
        
      下面我要说的是MIS系统权限管理的数据库设计及实现,当然,这些思路也可以推广开来应用,比如说在BBS中用来管理不同级别的用户权限。     
        
      权限设计通常包括数据库设计、应用程序接口(API)设计、程序实现三个部分。     
        
      这三个部分相互依存,密不可分,要实现完善的权限管理体系,必须考虑到每一个环节可行性与复杂程度甚至执行效率。     
        
      我们将权限分类,首先是针对数据存取的权限,通常有录入、浏览、修改、删除四种,其次是功能,它可以包括例如统计等所有非直接数据存取操作,另外,我们还可能对一些关键数据表某些字段的存取进行限制。除此,我想不出还有另外种类的权限类别。     
        
      完善的权限设计应该具有充分的可扩展性,也就是说,系统增加了新的其它功能不应该对整个权限管理体系带来较大的变化,要达到这个目的,首先是数据库设计合理,其次是应用程序接口规范。     
        
      我们先讨论数据库设计。通常我们使用关系数据库,这里不讨论基于Lotus产品的权限管理。     
        
      权限表及相关内容大体可以用六个表来描述,如下:     
      1   角色(即用户组)表:包括三个字段,ID,角色名,对该角色的描述;     
      2   用户表:包括三个或以上字段,ID,用户名,对该用户的描述,其它(如地址、电话等信息);     
      3   角色-用户对应表:该表记录用户与角色之间的对应关系,一个用户可以隶属于多个角色,一个角色组也可拥有多个用户。包括三个字段,ID,角色ID,用户ID;     
      4   限制内容列表:该表记录所有需要加以权限区分限制的数据表、功能和字段等内容及其描述,包括三个字段,ID,名称,描述;     
      5   权限列表:该表记录所有要加以控制的权限,如录入、修改、删除、执行等,也包括三个字段,ID,名称,描述;     
      6   权限-角色-用户对应表:一般情况下,我们对角色/用户所拥有的权限做如下规定,角色拥有明令允许的权限,其它一律禁止,用户继承所属角色的全部权限,在此范围内的权限除明令禁止外全部允许,范围外权限除明令允许外全部禁止。该表的设计是权限管理的重点,设计的思路也很多,可以说各有千秋,不能生搬硬套说某种方法好。对此,我的看法是就个人情况,找自己觉得合适能解决问题的用。     
        
      先说第一种也是最容易理解的方法,设计五个字段:ID,限制内容ID,权限ID,角色/用户类型(布尔型字段,用来描述一条记录记录的是角色权限还是用户权限),角色/用户ID,权限类型(布尔型字段,用来描述一条记录表示允许还是禁止)     
        
      好了,有这六个表,根据表六,我们就可以知道某个角色/用户到底拥有/禁止某种权限。     
        
      或者说,这么设计已经足够了,我们完全实现了所需要的功能:可以对角色和用户分别进行权限定制,也具有相当的可扩展性,比如说增加了新功能,我们只需要添加一条或者几条记录就可以,同时应用程序接口也无须改动,具有相当的可行性。但是,在程序实现的过程中,我们发现,使用这种方法并不是十分科学,例如浏览某个用户所拥有的权限时,需要对数据库进行多次(甚至是递归)查询,极不方便。于是我们需要想其它的办法。使用过Unix系统的人们都知道,Unix文件系统将对文件的操作权限分为三种:读、写和执行,分别用1、2、4三个代码标识,对用户同时具有读写权限的文件被记录为3,即1+2。我们也可以用类似的办法来解决这个问题。初步的想法是修改权限列表,加入一个字段:标识码,例如,我们可以将录入权限标识为1,浏览权限标识为2,修改权限标识为4,删除权限标识为8,执行权限标识为16,这样,我们通过权限累加的办法就可以轻易的将原本要分为几条记录描述的权限放在一起了,例如,假定某用户ID为1,库存表对应的限制内容ID为2,同时规定角色类型为0、用户类型为1,我们就可以将该用户具有录入、浏览、修改、删除库存表的权限描述为:2,15,1,1。     
        
      确实很简单,不是吗?甚至还有更过激的办法,将限制内容列表也加上一列,定义好标识码,这样,我们甚至可以用简单的一条记录描述某个用户具有的对全部内容所具有的全部权限了。当然,这样做的前提是限制内容数量比较小,不然,呵呵,2的n次方递增起来可是数量惊人,不容易解析的。     
        
      从表面上看,上述方法足以达到实现功能、简化数据库设计及实现的复杂度这个目的,但这样做有个弊端,我们所涉及的权限列表不是相互独立而是互相依赖的,比如说修改权限,其实是包含浏览权限的,例如,我们可能只是简单的设置用户对库存表存取的权限值为录入+修改+删除(1+4+8=13),但事实上,该用户具有(1+2+4+8=15)的权限,也就是说,在这种方案中,13=15。于是当我们调用API询问某用户是否具有浏览权限时,就必须判断该用户是否具有对该数据表的修改权限,因此,如果不能在程序中固化权限之间的包含关系,就不能利用应用程序接口简单的做出判断。但这与我们的目的“充分的可扩展性”矛盾。     
        
      这个问题如何解决?我想到了另外一种设置标识码的方法,那就是利用素数。我们不妨将录入、浏览、修改、删除、执行的基本标志码定为2,3,5,7,11,当遇到权限互相包含的时候,我们将它的标识码设定为两个(或多个)基本标志码的乘积,例如,可以将“修改”功能的标志码定为3*5=15,然后将所有的权限相乘,就得到了我们需要的最终权限标识值。这样,我们在询问用户是否具有某项权限的时候,只需要将最终的值分解成质因子,例如,我们可以定义一个用户具有录入+修改+删除库存表的权限为   2*15*7=2*3*5*7,即表示,该用户具有了对库存表录入+浏览+修改+删除权限。     
        
      当然,对权限列表我们使用上述方法的前提是权限列表记录条数不会太多并且关系不是十分复杂,否则,光是解析权限代码就要机器忽悠半宿:)  

    通用权限管理设计篇  


    http://sxiaomais.blog.163.com/blog/static/31741203200811102630406/

    一.引言

           因为做过的一些系统的权限管理的功能虽然在逐步完善,但总有些不尽人意的地方,总想抽个时间来更好的思考一下权限系统的设计。

           权限系统一直以来是我们应用系统不可缺少的一个部分,若每个应用系统都重新对系统的权限进行设计,以满足不同系统用户的需求,将会浪费我们不少宝贵时间,所以花时间来设计一个相对通用的权限系统是很有意义的。

    二.设计目标

           设计一个灵活、通用、方便的权限管理系统。

           在这个系统中,我们需要对系统的所有资源进行权限控制,那么系统中的资源包括哪些呢?我们可以把这些资源简单概括为静态资源(功能操作、数据列)和动态资源(数据),也分别称为对象资源和数据资源,后者是我们在系统设计与实现中的叫法。

    系统的目标就是对应用系统的所有对象资源和数据资源进行权限控制,比如应用系统的功能菜单、各个界面的按钮、数据显示的列以及各种行级数据进行权限的操控。

    三.相关对象及其关系

           大概理清了一下权限系统的相关概念,如下所示:

    1.       权限

    系统的所有权限信息。权限具有上下级关系,是一个树状的结构。下面来看一个例子

    系统管理

            用户管理

                   查看用户

                    新增用户

                         修改用户

                         删除用户

           对于上面的每个权限,又存在两种情况,一个是只是可访问,另一种是可授权,例如对于“查看用户”这个权限,如果用户只被授予“可访问”,那么他就不能将他所具有的这个权限分配给其他人。

    2.       用户

    应用系统的具体操作者,用户可以自己拥有权限信息,可以归属于0~n个角色,可属于0~n个组。他的权限集是自身具有的权限、所属的各角色具有的权限、所属的各组具有的权限的合集。它与权限、角色、组之间的关系都是n对n的关系。

    3.       角色

    为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推。

    4.       组

    为了更好地管理用户,对用户进行分组归类,简称为用户分组。组也具有上下级关系,可以形成树状视图。在实际情况中,我们知道,组也可以具有自己的角色信息、权限信息。这让我想到我们的QQ用户群,一个群可以有多个用户,一个用户也可以加入多个群。每个群具有自己的权限信息。例如查看群共享。QQ群也可以具有自己的角色信息,例如普通群、高级群等。

    针对上面提出的四种类型的对象,让我们通过图来看看他们之间的关系。

    通用权限管理设计篇 - sxiaomais - 小麦

     

        有上图中可以看出,这四者的关系很复杂,而实际的情况比这个图还要复杂,权限、角色、组都具有上下级关系,权限管理是应用系统中比较棘手的问题,要设计一个通用的权限管理系统,工作量也着实不小。

    当然对于有些项目,权限问题并不是那么复杂。有的只需要牵涉到权限和用户两种类型的对象,只需要给用户分配权限即可。

    在另一些情况中,引入了角色对象,例如基于角色的权限系统, 只需要给角色分配权限,用户都隶属于角色,不需要单独为用户分配角色信息。

    通用权限管理设计篇(二)——数据库设计

       国庆前整的通用权限设计的数据库初步设计部分,现在贴上来。

    理清了对象关系之后,让我们接着来进行数据库的设计。在数据库建模时,对于N对N的

    关系,一般需要加入一个关联表来表示关联的两者的关系。初步估计一下,本系统至少需要十张表,分别为:权限表、用户表、角色表、组表、用户权限关联表、用

    户角色关联表、角色权限关联表、组权限关联表、组角色关联表、用户属组关联表。当然还可能引出一些相关的表。下面让我们在PowerDesigner中画出各表吧。

           各表及其关系如下:

    通用权限管理设计篇 - sxiaomais - 小麦

          

    1.       用户表

    用户表(TUser)

    字段名称

    字段

    类型

    备注

    记录标识

    tu_id

    bigint

    pk, not null

    所属组织

    to_id

    bigint

    fk, not null

    登录帐号

    login_name

    varchar(64)

    not null

    用户密码

    password

    varchar(64)

    not null

    用户姓名

    vsername

    varchar(64)

    not null

    手机号

    mobile

    varchar(20)

    电子邮箱

    email

    varchar(64)

    创建时间

    gen_time

    datetime

    not null

    登录时间

    login_time

    datetime

    上次登录时间

    last_login_time

    datetime

    登录次数

    count

    bigint

    not null

    2.       角色表

    角色表(TRole)

    字段名称

    字段

    类型

    备注

    角色ID

    tr_id

    bigint

    pk, not null

    父级角色ID

    parent_tr_id

    bigint

    not null

    角色名称

    role_name

    varchar(64)

    not null

    创建时间

    gen_time

    datetime

    not null

    角色描述

    description

    varchar(200)

    3.       权限表

    权限表(TRight)

    字段名称

    字段

    类型

    备注

    权限ID

    tr_id

    bigint

    pk, not null

    父权限

    parent_tr_id

    bigint

    not null

    权限名称

    right_name

    varchar(64)

    not null

    权限描述

    description

    varchar(200)

    4.       组表

    组表(TGroup)

    字段名称

    字段

    类型

    备注

    组ID

    tg_id

    bigint

    pk, not null

    组名称

    group_name

    varchar(64)

    not null

    父组

    parent_tg_id

    bigint

    not null

    创建时间

    gen_time

    datetime

    not null

    组描述

    description

    varchar(200)

    5.       角色权限表

    角色权限表(TRoleRightRelation)

    字段名称

    字段

    类型

    备注

    记录标识

    trr_id

    bigint

    pk, not null

    角色

    Role_id

    bigint

    fk, not null

    权限

    right_id

    bigint

    fk, not null

    权限类型

    right_type

    int

    not null(0:可访问,1:可授权)

    6.       组权限表

    组权限表(TGroupRightRelation)

    字段名称

    字段

    类型

    备注

    记录标识

    tgr_id

    bigint

    pk, not null

    tg_id

    bigint

    fk, not null

    权限

    tr_id

    bigint

    fk, not null

    权限类型

    right_type

    int

    not null(0:可访问,1:可授权)

    7.       组角色表

    组角色表(TGroupRoleRelation)

    字段名称

    字段

    类型

    备注

    记录标识

    tgr_id

    bigint

    pk, not null

    tg_id

    bigint

    fk, not null

    角色

    tr_id

    bigint

    pk, not null

    8.       用户权限表

    用户权限表(TUserRightRelation)

    字段名称

    字段

    类型

    备注

    记录标识

    tur_id

    bigint

    pk, not null

    用户

    tu_id

    bigint

    fk, not null

    权限

    tr_id

    bigint

    fk, not null

    权限类型

    right_type

    int

    not null(0:可访问,1:可授权)

    9.       用户角色表

    用户角色表(TUserRoleRelation)

    字段名称

    字段

    类型

    备注

    记录标识

    tur_id

    bigint

    pk, not null

    用户

    tu_id

    bigint

    fk, not null

    角色

    tr_id

    bigint

    fk, not null

    10.   用户组表

    用户组表(TUserGroupRelation)

    字段名称

    字段

    类型

    备注

    记录标识

    tug_id

    bigint

    pk, not null

    用户

    tu_id

    bigint

    fk, not null

    tg_id

    bigint

    fk, not null

    11.   组织表

    组织表(TOrganization)

    字段名称

    字段

    类型

    备注

    组织id

    to_id

    bigint

    pk, not null

    父组

    parent_to_id

    bigint

    not null

    组织名称

    org_name

    varchar(64)

    not null

    创建时间

    gen_time

    datetime

    not null

    组织描述

    description

    varchar(200)

    12.   操作日志表

    操作日志表(TLog)

    字段名称

    字段

    类型

    备注

    日志ID

    log_id

    bigint

    pk, not null

    操作类型

    op_type

    int

    not null

    操作内容

    content

    varchar(200)

    not null

    操作人

    tu_id

    bigint

    fk, not null

    操作时间

    gen_time

    datetime

    not null

    通用权限管理系统设计篇(三)——概要设计说明书

     在前两篇文章中,不少朋友对我的设计提出了异议,认为过于复杂,当然在实际的各种系统的权限管理模块中,并不像这里设计得那么复杂,我以前所做的系统中,

    由只有用户和权限的,有只有用户、权限和角色的,还有一个系统用到了用户、权限、角色、组概念,这个系统是我在思考以前所做系统的权限管理部分中找到的一

    些共性而想到的一个设计方案,当然还会有不少设计不到位的地方,在设计开发过程中会慢慢改进,这个系统权当学习只用,各位朋友的好的建议我都会考虑到设计

    中,感谢各位朋友的支持。

        今天抽时间整了一份概念设计出来,还有一些地方尚未考虑清楚,贴出1.0版,希望各位朋友提出宝贵建议。

        大家也可以点击此处《通用权限管理概要设计说明书》自行下载,这是1.0版本,有些地方可能还会进行部分修改,有兴趣的朋友请关注我的blog。

         

    1.      引言

    1.1 编写目的

    本文档对通用权限管理系统的总体设计、接口设计、界面总体设计、数据结构设计、系统出错处理设计以及系统安全数据进行了说明。

    1.2 背景

    a、 软件系统的名称:通用权限管理系统;

    b、 任务提出者、开发者:谢星星;

    c、 在J2EE的web系统中需要使用权限管理的系统。

    1.3 术语

    本系统:通用权限管理系统;

    SSH:英文全称是Secure Shell。

    1.4 预期读者与阅读建议

    预期读者

    阅读重点

    开发人员

    总体设计、接口设计、数据结构设计、界面总体设计、系统出错处理设计

    设计人员

    总体设计、接口设计、数据结构设计、系统安全设计

    1.5 参考资料

    《通用权限管理系统需求规格说明书》

    《通用权限管理系统数据库设计说明书》

    2.      总体设计

    2.1 设计目标

    权限系统一直以来是我们应用系统不可缺少的一个部分,若每个应用系统都重新对系统的权限进行设计,以满足不同系统用户的需求,将会浪费我们不少宝贵时间,所以花时间来设计一个相对通用的权限系统是很有意义的。

    本系统的设计目标是对应用系统的所有资源进行权限控制,比如应用系统的功能菜单、各个界面的按钮控件等进行权限的操控。

    2.2 运行环境

    操作系统:Windows系统操作系统和Linux系列操作系统。

    2.3 网络结构

     通用权限管理系统可采用Java Swing实现,可以在桌面应用和Web应用系统中进行调用。如果需要要适应所有开发语言,可以将其API发布到WEB Service上。暂时用Java Swing实现。

    2.4 总体设计思路和处理流程

    在说明总体设计思路前,我们先说明本系统的相关概念:

    1. 权限资源

    系统的所有权限信息。权限具有上下级关系,是一个树状的结构。下面来看一个例子

    系统管理

            用户管理

                   查看用户

                   新增用户

                   修改用户

                   删除用户

    对于上面的每个权限,又存在两种情况,一个是只是可访问,另一种是可授权,例如对于“查看用户”这个权限,如果用户只被授予“可访问”,那么他就不能将他所具有的这个权限分配给其他人。

    2. 用户

    应用系统的具体操作者,用户可以自己拥有权限信息,可以归属于0~n个角色,可属于0~n个组。他的权限集是自身具有的权限、所属的各角色具有的权限、所属的各组具有的权限的合集。它与权限、角色、组之间的关系都是n对n的关系。

    3. 角色

    为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推。

    4. 组

    了更好地管理用户,对用户进行分组归类,简称为用户分组。组也具有上下级关系,可以形成树状视图。在实际情况中,我们知道,组也可以具有自己的角色信息、

    权限信息。这让我想到我们的QQ用户群,一个群可以有多个用户,一个用户也可以加入多个群。每个群具有自己的权限信息。例如查看群共享。QQ群也可以具有

    自己的角色信息,例如普通群、高级群等。

    针对如上提出的四种对象,我们可以整理得出它们之间的关系图,如下所示:

    通用权限管理设计篇 - sxiaomais - 小麦

    总体设计思路是将系统分为组权限管理、角色权限管理、用户权限管理、组织管理和操作日志管理五部分。

    其中组权限管理包括包含用户、所属角色、组权限资源和组总权限资源四部分,某个组的权限信息可用公式表示:组权限 = 所属角色的权限合集 + 组自身的权限。

    角色权限管理包括包含用户、包含组和角色权限三部分,某个角色的权限的计算公式为:角色权限 = 角色自身权限。

    用户权限管理包括所属角色、所属组、用户权限、用户总权限资源和组织管理五部分。某个用户总的权限信息存在如下计算公式:用户权限 = 所属角色权限合集 + 所属组权限合集 + 用户自身权限。

    组织管理即对用户所属的组织进行管理,组织以树形结构展示,组织管理具有组织的增、删、改、查功能。

    操作日志管理用于管理本系统的操作日志。

    注意:因为组和角色都具有上下级关系,所以下级的组或角色的权限只能在自己的直属上级的权限中选择,下级的组或者角色的总的权限都不能大于直属上级的总权限。

    2.5 模块结构设计

    本系统的具有的功能模块结构如下图所示:

    通用权限管理设计篇 - sxiaomais - 小麦

    2.6 尚未解决的问题

    无。

    3.      接口设计(暂略)

    3.1 用户接口(暂略)

    3.2 外部接口(暂略)

    3.3 内部接口(暂略)

    4.      界面总体设计

    本节将阐述用户界面的实现,在此之前对页面元素做如下约定:

    序号

    页面元素

    约定

    1

    按钮

    未选中时:[按钮名称]

    选中时:[按钮名称]

    2

    单选框

    ○ 选项

    3

    复选框

    □ 选项

    4

    下拉框

     [选项,…,] ▽

    5

    文本框

     |________|

    6

    TextArea

     |…………|

    7

    页签

    未选中时:选项名称

     选中时:选项名称

    8

    未选中链接

    链接文字

    9

    选中链接

    链接文字

    10

    说明信息

    说明信息

     

    4.1 组权限管理

    4.1.1包含用户

    组信息

       组1

           组11

           组12

           组…

       组2

           组21

           组22

           组…

     

    所选择组:组1

    [包含用户] [所属角色] [组权限] [总权限]

    [修改]

    用户名   姓名     手机号   最近登录时间 登录次数

    阿蜜果 谢星星 13666666666 2007-10-8    66

    sterning xxx    13555555555 2007-10-8    10 

    ……

    当用户选择“修改”按钮时,弹出用户列表,操作人可以通过勾选或取消勾选来修改该组所包含的用户。

    4.1.2所属角色

    组信息

       组1

           组11

           组12

           组…

       组2

           组21

           组22

           组…

     

    所选择组:组1

    [包含用户] [所属角色] [组权限] [总权限]

    [修改]

    角色ID   角色名称   角色描述

    1          访客       --

       2         初级用户    --

      

    当用户选择“修改”按钮时,弹出角色树形结构,操作人可以通过勾选或取消勾选来修改该组所属的角色。

    4.1.3组权限

    组信息

       组1

           组11

           组12

           组…

       组2

           组21

           组22

           组…

     

    所选择组:组1

    [包含用户] [所属角色] [组权限] [总权限]

    通用权限管理设计篇 - sxiaomais - 小麦

                    [保存] [取消]

    4.1.4总权限

    组信息

       组1

           组11

           组12

           组…

       组2

           组21

           组22

           组…

     

    所选择组:组1

    [包含用户] [所属角色] [组权限] [总权限]

    通用权限管理设计篇 - sxiaomais - 小麦

                    [保存] [取消]

    通过对已具有的权限取消勾选,或为某权限添加勾选,来修改组的权限信息,点击“保存”按钮保存修改信息。

    4.1.5组管理

           在下图中,选中组1的时候,右键点击可弹出组的操作列表,包括添加、删除和修改按钮,从而完成在该组下添加子组,删除该组以及修改该组的功能。

    组信息

       组1

           组11

           组12

           组…

       组2

           组21

           组22

           组…

     

    所选择组:组1

    [包含用户] [所属角色] [组权限] [总权限]

    [修改]

    用户名   姓名     手机号   最近登录时间 登录次数

    阿蜜果 谢星星 13666666666 2007-10-8    66

    sterning xxx    13555555555 2007-10-8    10 

    ……

    4.2 角色权限管理

    4.2.1包含用户

    角色信息

       角色1

           角色11

           角色12

           角色…

       角色2

           角色21

           角色22

           角色…

     

    所选择角色:角色1

    [包含用户] [包含组] [角色权限]

    [修改]

    用户名   姓名     手机号   最近登录时间 登录次数

    阿蜜果 谢星星 13666666666 2007-10-8    66

    sterning xxx    13555555555 2007-10-8    10 

    ……

    当用户选择“修改”按钮时,弹出用户列表,操作人可以通过勾选或取消勾选来修改该角色所包含的用户。

    4.2.2包含组

    角色信息

       角色1

           角色11

           角色12

           角色…

       角色2

           角色21

           角色22

           角色…

     

    所选择角色:角色1

    [包含用户] [包含组] [角色权限]

    [修改]

    组ID   组名称     组描述

    1      xxx1       --

    2       xxx2        -- 

    ……

    当用户选择“修改”按钮时,弹出用户列表,操作人可以通过勾选或取消勾选来修改该角色所包含的组。

    4.2.3角色权限

    角色信息

       角色1

           角色11

           角色12

           角色…

       角色2

           角色21

           角色22

           角色…

     

    所选择角色:角色1

    [包含用户] [包含组] [角色权限]

                     通用权限管理设计篇 - sxiaomais - 小麦

                   [保存] [取消]

    通过对已具有的权限取消勾选,或为某权限添加勾选,来修改角色的权限信息,点击“保存”按钮保存修改信息。

    4.2.4管理角色

           在下图中,选中组1的时候,右键点击可弹出组的操作列表,包括添加、删除和修改按钮,从而完成在该组下添加子组,删除该组以及修改该组的功能。

    角色信息

       角色1

           角色11

           角色12

           角色…

       角色2

           角色21

           角色22

           角色…

     

    所选择角色:角色1

    [包含用户] [包含组] [角色权限]

    [修改]

    用户名   姓名     手机号   最近登录时间 登录次数

    阿蜜果 谢星星 13666666666 2007-10-8    66

    sterning xxx    13555555555 2007-10-8    10 

    ……

    4.3 用户权限管理

    4.3.1所属角色

    用户权限信息

    xx公司

       广州分公司

           阿蜜果

           肖xx

           yy…

       北京分公司

           zz1

           zz2

           zz3…

     

    所选择用户:阿蜜果

    [所属角色] [所属组] [用户权限] [总权限]

    [修改]

    角色ID   角色名称   角色描述

    1          访客       --

       2         初级用户    --

    当用户选择“修改”按钮时,弹出角色树形结构,操作人可以通过勾选或取消勾选来修改该用户所属的角色。

    4.3.2所属组

    用户信息

    xx公司

       广州分公司

           阿蜜果

           肖xx

           yy…

       北京分公司

           zz1

           zz2

           zz3…

     

    所选择用户:阿蜜果

    [所属角色] [所属组] [用户权限] [总权限]

    [修改]

    组ID   组名称     组描述

    1       组1         --

       2       组2         --

    当用户选择“修改”按钮时,弹出组的树形结构,操作人可以通过勾选或取消勾选来修改该用户所属的组。

    4.3.3用户权限

    用户信息

    xx公司

       广州分公司

           阿蜜果

           肖xx

           yy…

       北京分公司

           zz1

           zz2

           zz3…

     

    所选择用户:阿蜜果

    [所属角色] [所属组] [用户权限] [总权限]

                     通用权限管理设计篇 - sxiaomais - 小麦

                    [保存] [取消]

    通过对已具有的权限取消勾选,或为某权限添加勾选,来修改用户的权限信息,点击“保存”按钮保存修改信息。

    4.3.4总权限

    用户信息

    xx公司

       广州分公司

           阿蜜果

           肖xx

           yy…

       北京分公司

           zz1

           zz2

           zz3…

     

    所选择用户:阿蜜果

    [所属角色] [所属组] [用户权限] [总权限]

                     通用权限管理设计篇 - sxiaomais - 小麦

                    [保存] [取消]

    通过对已具有的权限取消勾选,或为某权限添加勾选,来修改用户的权限信息,点击“保存”按钮保存修改信息。

    4.3.5用户管理

           当选择了某用户时,点击右键,弹出菜单列表:修改、删除、取消,点击修改和删除按钮可以实现用户的删除和修改功能。

           选择某个组织,例如下表中的“广州分公司”,弹出菜单列表:添加子组织、删除组织、修改组织、添加用户、取消,点击添加用户按钮可以实现用户的添加功能。

    用户权限信息

    xx公司

       广州分公司

           阿蜜果

           肖xx

           yy…

       北京分公司

           zz1

           zz2

           zz3…

     

    所选择用户:阿蜜果

    [所属角色] [所属组] [用户权限] [总权限]

    [修改]

    角色ID   角色名称   角色描述

    1          访客       --

       2         初级用户    --

    4.3.6组织管理

           选择某个组织,例如下表中的“广州分公司”,弹出菜单列表:添加子组织、删除组织、修改组织、添加用户、取消,点击添加子组织、删除组织、修改组织按钮可以实现组织的添加、删除和修改功能。

    用户权限信息

    xx公司

       广州分公司

           阿蜜果

           肖xx

           yy…

       北京分公司

           zz1

           zz2

           zz3…

     

    所选择用户:阿蜜果

    [所属角色] [所属组] [用户权限] [总权限]

    [修改]

    角色ID   角色名称   角色描述

    1          访客       --

       2         初级用户    --

    4.4 操作日志管理

    4.4.1查询操作日志

    操作名称:|________|  操作人:|________|

    操作时间从 |________| 到 |________| [查询] [重置] [删除]

    编号    操作名称    操作内容    操作人    操作时间

    1        xx1         --        Amigo    2007-10-8

    2        xx2         --        xxyy     2007-10-8

    输入上图表单中的查询信息后,点击“查询”按钮,可查询出符合条件的信息。

    4.4.2删除操作日志

    操作名称:|________| 操作人:|________|

    操作时间从 |________| 到 |________| [查询] [重置] [删除]

    编号    操作名称    操作内容    操作人    操作时间

    1        xx1       --           Amigo      2007-10-8

    2        xx2       --           xxyy       2007-10-8

    输入上图表单中的查询信息后,点击“查询”按钮,可查询出符合条件的信息。而后点击“删除”按钮,可删除符合查询条件的操作日志。

    5.      数据结构设计

    数据库设计的模型请参见《通用权限管理系统_数据库模型.pdm》。表的说明请参见《通用权限管理系统数据库设计说明书》。

    5.1 设计原则

    5.1.1命名的规范

    数据库中表、主键、外键、索引的命名都以统一的规则,采用大小写敏感的形式,各种对象命名长度不要超过30个字符,这样便于应用系统适应不同的数据库平台。

    5.1.2数据的一致性和完整性

    为了保证数据库的一致性和完整性,往往通过表间关联的方式来尽可能的降低数据的冗余。表间关联是一种强制性措施,建立后,对父表(Parent Table)和子表(Child Table)的插入、更新、删除操作均要占用系统的开销。如果数据冗余低,数据的完整性容易得到保证,但增加了表间连接查询的操作,为了提高系统的响应时间,合理的数据冗余也是必要的。使用规则(Rule)和约束(Check)来防止系统操作人员误输入造成数据的错误是设计人员的另一种常用手段,但是,不必要的规则和约束也会占用系统的不必要开销,需要注意的是,约束对数据的有效性验证要比规则快。所有这些,需要在设计阶段应根据系统操作的类型、频度加以均衡考虑。

    5.2 数据库环境说明

    数据库:MySql5.0

    设计库建模工具:PowerDesigner12.0

    5.3 数据库命名规则

    表名以T开头,外键以FK开头,索引以INDEX开头。

    5.4 逻辑结构

    pdm文件的名称为:《通用权限管理系统_数据库模型》。

    5.5 物理存储

    通过数据库建模工具PowerDesigner12可以将pdm导出为文本文件,将数据库脚本放入文本文件中保存。

    5.6 数据备份和恢复

    数据库需定期备份(每天备份一次),备份文件格式为backup_yyyyMMdd,数据库被破坏时,利用最新的备份文件进行恢复。

    6.      系统出错处理设计

    6.1 出错信息

    错误分类

    子项及其编码

    错误名称

    错误代码

    备注

    数据库错误

    连接

    连接超时

    100001001

    连接断开

    100001002

    数据库本身错误代码

    数据库本身错误代码

    100002+数据库错误代码

    TCP连接错误

    连接

    连接超时

    101001001

    连接断开

    101001002

    其它TCP连接错误(socket自身错误代码)

    101002+ socket错误代码

    配置信息错误

    未配置输入参数

    102001

    未配置输出参数

    102002

    组管理部分自定义错误

    103001——103999

    角色管理部分自定义错误

    104001——104999

    用户管理部分自定义错误

    105001——105999

    操作日志管理

    106001——106999

    6.2 补救措施

    为了当某些故障发生时,对系统进行及时的补救,提供如下补救措施:

    a.后备技术   定期对数据库信息进行备份(每天一次),当数据库因某种原因被破坏时,以最新的数据库脚本进行恢复;。

    7.      系统安全设计

    7.1 数据传输安全性设计

    SSH可以通过将联机的封包加密的技术进行资料的传递; 使用SSH可以把传输的所有数据进行加密,即使有人截获到数据也无法得到有用的信息。同时数据经过压缩,大大地加快了传输的速度。通过SSH的使用,可以确保资料传输比较安全并且传输效率较高。

    7.2 应用系统安全性设计

    操作人的操作信息需要提供操作记录。对系统的异常信息需进行记录,已备以后查看。只有授权用户才能登录系统,对于某个操作,需要具有相应权限才能进行操作。

    7.3 数据存储安全性设计           

    对于用户的密码等敏感信息采用MD5进行加密。



    OA之权限管理

    权限管理自己做完了,但是很多的研究和总结,现在就来总结一下权限管理。

    第一、数据库中主要类:

    主要负责类:用户(User),角色(Role)、资源(module)和操作(Permission)

    衍生类:用户角色(UserRole)和对某个资源的某个操作(ACL)

    第二、ACL的具体理解:

    一条acl授权记录中主要记录了以下信息: 角色、资源和授权 

    授权作为一个int, 每一位是一个操作的权限. 

    假设从右向左, 分别代表CRUD 

    那么, 我们CRUD的代码就应该是0123(也就是移位时要移的位数), 因为我们要进行移位进行认证。

    先看授权与取消授权的代码:

    [java] view plaincopy
    1. public void setPermission(int permission,boolean yes){  
    2.         int temp = 1;  
    3.         temp = temp << permission;  
    4.         if(yes){  
    5.             aclState |= temp;  
    6.         }else{  
    7.             aclState &= ~temp;  
    8.         }  
    9.     }  

    首先, 一个int temp = 1的临时变量, aclState为原始授权状态

    tmp的二进制表示是: 00000000 00000000 00000000 00000001 


    U对应的代码是U, 对应的是2. 


    将tmp左移2位, temp = tmp < < 2; temp变成:00000000 00000000 00000000 00000100 


    假设原始授权是aclState=00000000 00000000 00000000 00001010 


    当变量yes=true时,为授权,将temp与aclState求|运算,因为temp现在只有他要授权的位为1,求或运算后,


    aclState=00000000 00000000 00000000 00001110,这样就授权成功


    当变量yes=false时,为取消授权,先将temp取反,即为11111111 11111111 11111111 11111011,


    现在只有要取消权限的位为0,其余全为1,然后与aclState求&运算,则除了要取消权限的位变0,其余的都不变,


    即aclState=00000000 00000000 00000000 00001010


    再来看认证:

    [java] view plaincopy
    1. public int getPermission(int permission){  
    2.                  int temp = 1;  
    3.     temp = temp << permission;  
    4.     temp &= aclState;  
    5.     if(temp != 0){  
    6.         return ACL_YES;  
    7.     }  
    8.     return ACL_NO;  
    9. }  

    认证更简单,直接将temp变量与aclState求&,temp为1的位为要验证的权限,其余全为0,如果aclState的这一位为1,则结果不为零,即有该权限;若aclState这一位为0,即没权限,则结果为0,没有该操作权限

    总结:权限管理最难理解的就是CRUD中的数据得来,至于别的我们可以关系,我们还是可以清晰的理解,还有一个概念就是集成的概念:

    a)        针对某个资源的所有操作,我们可以设置这些权限对用户来说是“继承”或“不继承”

                            i.             继承:意思是这些权限将使用其(即用户)所拥有的角色的权限,而不使用其(即用户)单独设置的权限

                          ii.             不继承:意思是这些权限将使用其单独设置的权限,而不使用其所拥有的角色的权限

    展开全文
  • MVC+EF框架+EasyUI实现权限管理 源码程序

    万次下载 热门讨论 2012-12-13 18:53:53
    MVC+EF框架+EasyUI实现权限管理是对权限的基本操作的操作,具体的可以参看我的博客http://www.cnblogs.com/hanyinglong/
  • 3. 掌握基于url的权限管理(不使用Shiro权限框架的情况下实现权限管理) 4. shiro实现用户认证 5. shiro实现用户授权 6. shiro与企业web项目整合开发的方法 权限管理原理知识 什么是权限管理 只要有用户参与的系统一般...

    知识清单

    1.了解基于资源的权限管理方式
    2. 掌握权限数据模型
    3. 掌握基于url的权限管理(不使用Shiro权限框架的情况下实现权限管理)
    4. shiro实现用户认证
    5. shiro实现用户授权
    6. shiro与企业web项目整合开发的方法

    权限管理原理知识

    什么是权限管理

    只要有用户参与的系统一般都要有权限管理,权限管理实现对用户访问系统的控制。按照安全规则或安全策略控制用户可以访问而且只能访问自己被授权的资源。
    权限管理包括用户认证和用户授权两部分。

    用户认证

    用户认证概念

    用户认证—— 用户去访问系统,系统需要验证用户身份的合法性。最常用的用户身份认证方法:1.用户密码方式、2.指纹打卡机、3.基于证书的验证方法。系统验证用户身份合法,用户方可访问系统的资源。

    用户认证流程


    关键对象

    subject:主体,理解为用户,可能是程序,都要去访问系统的资源,系统需要对subject进行身份认证。
    principal:身份信息,通常是唯一的,一个主体可以有多个身份信息,但是只能有一个主身份信息(primary  principal)。
    credential:凭证信息,可以是密码、证书、指纹等。
    总结:主体在进行身份认证时需要提供身份信息和凭证信息。

    用户授权

    用户授权概念

    用户授权,简单理解为访问控制,在用户认证通过后,系统对用户访问资源进行控制,当用户具有资源的访问权限方可访问。

    授权流程


    其中橙色为授权流程

    关键对象

    授权的过程可以理解为  who  对 what(which) 进行how操作
    who:主体,即subject,subject在认证通过后,系统进行访问控制。
    what(which):资源(Resource) ,subject必须具备资源访问权限才可以访问该资源。资源包括很多方面比如:用户列表页面、商品修改菜单、商品id为001的商品信息。
    资源分为资源类型和资源实例
    例如系统的用户信息就是资源类型,相当于Java类。
    系统中id为001的用户就是资源实例,相当于new的Java对象。
    how:权限/许可(permission),针对资源的权限或许可,subject必须具有permission方可访问资源,如何访问/操作需要定义permission,权限比如:用户添加、用户添加、商品删除。

    权限模型

    主体(账号、密码)
    资源(资源名称,访问地址)
    权限(权限名称、资源id)
    角色(角色名称)
    角色和权限关系(角色id、权限id)
    如下图:

    通常企业开发中将资源和权限合并为一张权限表,如下:
    资源(资源名称、访问地址)
    权限(权限名称、资源id)
    合并为:
    权限(权限名称、资源名称、资源访问地址)

    上图被称为权限管理的通用模型,不过在企业开发中根据系统自身特点还会对上图进行修改,但是用户、角色、权限、用户角色关系、角色权限关系是必不可少的。

    分配权限

    用户需要分配相应的权限才可以访问相应的资源。权限是对资源的操作许可。
    通常给用户分配资源权限需要将权限信息持久化,比如存储在关系数据库中。
    把用户信息、权限管理、用户分配的权限信息写入到数据库(权限数据模型)。

    权限控制(授权核心)

    基于角色的访问控制

    RBAC (Role  based access  control) 基于角色的访问控制
    比如:
    系统角色包括:部门经理、总经理...(角色针对用户进行划分)
    系统中代码实现:
    //如果该user是部门经理则可以访问if中的代码
    if(user.getRole("部门经理")){
        // 系统资源内容
        // 用户报表查看
    }
    问题:
    角色是针对人进行划分的,人作为用户在系统中属于活动内容,如果该角色可以访问的资源出现变更,则需要修改代码,比如:需要变更为部门经理和总经理都可以进行用户报表查看,代码改为:
    if(user.getRole("部门经理") || user.getRole("总经理")){
        // 系统资源内容
        // 用户报表查看
    }
    由此可以发现基于角色的访问控制是不利于系统维护的(可扩展性不强)

    基于资源的访问控制

    RBAC (Resource  based  access control)  基于资源的访问控制
    资源在系统中是不变的,比如资源有:类中的方法,页面中的按钮
    对资源的访问需要具有permission权限,代码可以写为:
    if(user.hasPermission("用户报表查看(权限标识符)")){
        // 系统资源内容
        // 用户报表查看
    }
    上面的方法就可以解决用户角色变更而不用修改上边权限控制的代码。
    如果需要变更权限只需要在分配权限模块去操作,给部门经理或总经理增加或解除权限
    建议使用基于资源的访问控制实现权限管理。

    权限管理解决方案

    什么是粗粒度权限和细粒度权限?

    粗粒度权限管理,是对资源类型的管理,资源类型比如:菜单、url连接、用户添加页面、用户信息、类方法、页面中按钮。
    粗粒度权限管理比如:超级管理员可以访问用户添加页面、用户信息等全部页面。
    部门管理员可以访问用户信息页面,包括页面中所有按钮。

    细粒度的权限管理,对资源实例的权限管理。资源实例就是资源类型的具体化,比如:用户id为001的修改连接,1110班的用户信息、行政部的员工。
    细粒度的权限管理就是数据级别的权限管理。
    细粒度权限管理比如:部门经理只可以访问本部门的员工信息,用户只可以看到自己的菜单,大区经理只能查看本辖区的销售订单...

    粗粒度和细粒度例子:
    系统中有一个用户查询页面,对用户列表查询分权限,如粗粒度管理,张三和李四都有用户列表查询的权限,张三和李四都可以访问用户列表查询。
    进一步进行细粒度的管理,张三(行政部)和李四(开发部)只可以查询自己本部门的用户信息,张三只能查看行政部的用户信息,李四只能查询开发部门的用户信息。细粒度的权限管理就是数据级别的权限管理。

    如何实现粗粒度和细粒度的权限管理

    如何实现粗粒度的权限管理?
    粗粒度权限管理比较容易将权限管理代码抽取出来在系统架构级别统一管理。比如:通过SpringMVC的拦截器实现授权。
    如何实现细粒度的权限管理?
    对细粒度的权限管理在数据级别是没有共性可言的,针对细粒度的权限管理就是系统业务逻辑的一部分,如果在业务层去处理相对简单,如果将细粒度的权限管理统一在系统架构级别去抽取,比较困难,即使进行了抽取,功能也可能存在扩展性不全的弊端。建议细粒度权限管理放在业务层去控制。比如:部门经理只查询本部门员工信息,在Service接口提供一个部门id的参数,controller中根据当前用户信息得到该用户属于哪个部门,调用service时将部门id传入service,实现该用户只查询本部门的员工。

    基于url拦截的方式实现

    基于url拦截的方式实现在实际开发中是比较常用的一种方式。
    对于web系统,通过filter过滤器实现url拦截,也可以通过SpringMVC的拦截器实现基于URL的拦截。

    使用权限管理框架来实现

    对于粗粒度的权限管理,建议使用优秀的权限管理框架进行实现,节省开发成本,提高开发效率。
    Shiro就是一个优秀的权限管理框架。

    基于URL的权限管理

    基于url的权限管理流程



    搭建环境

    数据库

    MySQL数据库中创建表:用户表、角色表、权限表(实质是权限和资源的结合)、用户角色关系表、角色权限关系表

    新建数据库shiro, 为了节约测试时间,在SpringMVC+mybatis基础之上进行整合(导入以前的基本数据),并导入权限数据如下:

    有关权限的SQL脚本如下:
    shiro_sql_table.sql
    /*
    SQLyog v10.2 
    MySQL - 5.1.72-community : Database - shiro
    *********************************************************************
    */
    
    
    /*!40101 SET NAMES utf8 */;
    
    /*!40101 SET SQL_MODE=''*/;
    
    /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
    /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
    /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
    /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
    /*Table structure for table `sys_permission` */
    
    CREATE TABLE `sys_permission` (
      `id` bigint(20) NOT NULL COMMENT '主键',
      `name` varchar(128) NOT NULL COMMENT '资源名称',
      `type` varchar(32) NOT NULL COMMENT '资源类型:menu,button,',
      `url` varchar(128) DEFAULT NULL COMMENT '访问url地址',
      `percode` varchar(128) DEFAULT NULL COMMENT '权限代码字符串',
      `parentid` bigint(20) DEFAULT NULL COMMENT '父结点id',
      `parentids` varchar(128) DEFAULT NULL COMMENT '父结点id列表串',
      `sortstring` varchar(128) DEFAULT NULL COMMENT '排序号',
      `available` char(1) DEFAULT NULL COMMENT '是否可用,1:可用,0不可用',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*Table structure for table `sys_role` */
    
    CREATE TABLE `sys_role` (
      `id` varchar(36) NOT NULL,
      `name` varchar(128) NOT NULL,
      `available` char(1) DEFAULT NULL COMMENT '是否可用,1:可用,0不可用',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*Table structure for table `sys_role_permission` */
    
    CREATE TABLE `sys_role_permission` (
      `id` varchar(36) NOT NULL,
      `sys_role_id` varchar(32) NOT NULL COMMENT '角色id',
      `sys_permission_id` varchar(32) NOT NULL COMMENT '权限id',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*Table structure for table `sys_user` */
    
    CREATE TABLE `sys_user` (
      `id` varchar(36) NOT NULL COMMENT '主键',
      `usercode` varchar(32) NOT NULL COMMENT '账号',
      `username` varchar(64) NOT NULL COMMENT '姓名',
      `password` varchar(32) NOT NULL COMMENT '密码',
      `salt` varchar(64) DEFAULT NULL COMMENT '盐',
      `locked` char(1) DEFAULT NULL COMMENT '账号是否锁定,1:锁定,0未锁定',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*Table structure for table `sys_user_role` */
    
    CREATE TABLE `sys_user_role` (
      `id` varchar(36) NOT NULL,
      `sys_user_id` varchar(32) NOT NULL,
      `sys_role_id` varchar(32) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
    /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
    /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
    /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
    
    shiro_sql_table_data.sql
    /*
    SQLyog v10.2 
    MySQL - 5.1.72-community : Database - shiro
    *********************************************************************
    */
    
    
    /*!40101 SET NAMES utf8 */;
    
    /*!40101 SET SQL_MODE=''*/;
    
    /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
    /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
    /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
    /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
    /*Data for the table `sys_permission` */
    
    insert  into `sys_permission`(`id`,`name`,`type`,`url`,`percode`,`parentid`,`parentids`,`sortstring`,`available`) values 
    (1,'权限','','',NULL,0,'0/','0','1'),(11,'商品管理','menu','/item/queryItem.action',NULL,1,'0/1/','1.','1'),
    (12,'商品新增','permission','/item/add.action','item:create',11,'0/1/11/','','1'),
    (13,'商品修改','permission','/item/editItem.action','item:update',11,'0/1/11/','','1'),
    (14,'商品删除','permission','','item:delete',11,'0/1/11/','','1'),
    (15,'商品查询','permission','/item/queryItem.action','item:query',11,'0/1/15/',NULL,'1'),
    (21,'用户管理','menu','/user/query.action','user:query',1,'0/1/','2.','1'),
    (22,'用户新增','permission','','user:create',21,'0/1/21/','','1'),
    (23,'用户修改','permission','','user:update',21,'0/1/21/','','1'),
    (24,'用户删除','permission','','user:delete',21,'0/1/21/','','1');
    
    /*Data for the table `sys_role` */
    
    insert  into `sys_role`(`id`,`name`,`available`) values 
    	('ebc8a441-c6f9-11e4-b137-0adc305c3f28','商品管理员','1'),
    	('ebc9d647-c6f9-11e4-b137-0adc305c3f28','用户管理员','1');
    
    /*Data for the table `sys_role_permission` */
    
    insert  into `sys_role_permission`(`id`,`sys_role_id`,`sys_permission_id`) values 
    	('ebc8a441-c6f9-11e4-b137-0adc305c3f21','ebc8a441-c6f9-11e4-b137-0adc305c','12'),
    	('ebc8a441-c6f9-11e4-b137-0adc305c3f22','ebc8a441-c6f9-11e4-b137-0adc305c','11'),
    	('ebc8a441-c6f9-11e4-b137-0adc305c3f24','ebc9d647-c6f9-11e4-b137-0adc305c','21'),
    	('ebc8a441-c6f9-11e4-b137-0adc305c3f25','ebc8a441-c6f9-11e4-b137-0adc305c','15'),
    	('ebc9d647-c6f9-11e4-b137-0adc305c3f23','ebc9d647-c6f9-11e4-b137-0adc305c','22'),
    	('ebc9d647-c6f9-11e4-b137-0adc305c3f26','ebc8a441-c6f9-11e4-b137-0adc305c','13');
    
    /*Data for the table `sys_user` */
    
    insert  into `sys_user`(`id`,`usercode`,`username`,`password`,`salt`,`locked`) values 
    	('lisi','lisi','李四','bf07fd8bbc73b6f70b8319f2ebb87483','uiwueylm','0'),
    	('zhangsan','zhangsan','张三','cb571f7bd7a6f73ab004a70322b963d5','eteokues','0');
    
    /*Data for the table `sys_user_role` */
    
    insert  into `sys_user_role`(`id`,`sys_user_id`,`sys_role_id`) values 
    	('ebc8a441-c6f9-11e4-b137-0adc305c3f28','zhangsan','ebc8a441-c6f9-11e4-b137-0adc305c'),
    	('ebc9d647-c6f9-11e4-b137-0adc305c3f28','lisi','ebc9d647-c6f9-11e4-b137-0adc305c');
    
    /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
    /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
    /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
    /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
    查看对应权限模型的数据如下:
    sys_user  用户表数据

    sys_role  角色表

    sys_permission 权限表

    sys_user_role  用户角色关系表

    sys_role_permission  角色权限关系表

    开发环境

    JDK1.8
    MyEclipse
    技术架构:SpringMVC+Mybatis+jQuery easyUI

    系统工程架构


    系统登录

    系统登录相当于用户身份认证,用户登录成功,要在Session中记录用户的身份信息。
    操作流程:
    用户进入登录页面。
    输入用户名和密码进行登陆。
    进行用户名和密码校验。
    如果校验通过,在Session中记录用户身份信息。

    用户的身份信息

    创建专门类用于记录用户身份信息。
    /**
     * 用户身份信息,存入Session  由于Tomcat正常关闭时会将Session序列化的本地硬盘上,所以实现Serializable接口
     * @author liuxun
     *
     */
    public class ActiveUser implements Serializable {
    	private String userid; //用户id(主键)
    	private String usercode; // 用户账号
    	private String username; // 用户姓名
    	....
            ....
    }

    mapper

    mapper接口:根据用户账号查询用户(sys_user)信息 (使用逆向工程生成权限相关的PO类和mapper接口)
    如下所示:
      
    将生成的代码拷贝到项目中

    service(进行用户名和密码校验)

    接口功能:根据用户的身份和密码进行认证,如果认证通过,返回用户身份信息。
    认证过程:
    根据用户身份(账号)查询数据库,如果查询不到 则抛出用户不存在
    对输入的密码和数据库密码进行比对,如果一致,认证通过。
    新建权限管理Service接口 添加身份认证方法
    /**
     * 认证授权服务接口
     * @author liuxun
     *
     */
    public interface SysService {
    	//根据用户的身份和密码进行认证,如果认证通过,返回用户身份信息
    	public ActiveUser authenticat(String usercode,String password) throws Exception;
    	
    	//根据用户账号查询用户信息
    	public SysUser findSysUserByUserCode(String userCode) throws Exception;
            ......
    }
    方法实现:
    public class SysServiceImpl implements SysService {
    	@Autowired
    	private SysUserMapper sysUserMapper;
    
    	public ActiveUser authenticat(String usercode, String password) throws Exception {
    
    		/**
    		 * 认证过程: 根据用户身份(账号)查询数据库,如果查询不到则用户不存在 
    		 * 对输入的密码和数据库密码进行比对,如果一致则认证通过
    		 */
    		// 根据用户账号查询数据库
    		SysUser sysUser = this.findSysUserByUserCode(usercode);
    
    		if (sysUser == null) {
    			// 抛出异常
    			throw new CustomException("用户账号不存在");
    		}
    
    		// 数据库密码(MD5加密后的密码)
    		String password_db = sysUser.getPassword();
    
    		// 对输入的密码和数据库密码进行比对,如果一致,认证通过
    		// 对页面输入的密码进行MD5加密
    		String password_input_md5 = new MD5().getMD5ofStr(password);
    		if (!password_db.equalsIgnoreCase(password_input_md5)) {
    			//抛出异常
    			throw new CustomException("用户名或密码错误");
    		}
    		//得到用户id
    		String userid = sysUser.getId();
    		
    		//认证通过,返回用户身份信息
    		ActiveUser activeUser = new ActiveUser();
    		activeUser.setUserid(userid);
    		activeUser.setUsercode(usercode);
    		activeUser.setUsername(sysUser.getUsername());
    
    		return activeUser;
    	}
    
    	public SysUser findSysUserByUserCode(String userCode) throws Exception {
    		SysUserExample sysUserExample = new SysUserExample();
    		SysUserExample.Criteria criteria = sysUserExample.createCriteria();
    		criteria.andUsercodeEqualTo(userCode);
    
    		List<SysUser> list = sysUserMapper.selectByExample(sysUserExample);
    		if (list != null && list.size() > 0) {
    			return list.get(0);
    		}
    
    		return null;
    	}
       
           ......
    }
    配置Service,往类Service中使用@Autowire 需要注册Service 注册有两种方法(注解或配置文件),在架构时没有配置扫描Service  需要在配置文件中注册Service
    <!-- 认证和授权的Service -->
       <bean id="sysService" class="liuxun.ssm.service.impl.SysServiceImpl"></bean>

    controller(记录Session)

    //用户登录提交方法
    	@RequestMapping("/login")
    	public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{
    		// 校验验证码,防止恶性攻击
    		// 从Session中获取正确的验证码
    		String validateCode = (String) session.getAttribute("validateCode");
    		
    		//输入的验证码和Session中的验证码进行对比
    		if (!randomcode.equalsIgnoreCase(validateCode)) {
    			//抛出异常
    			throw new CustomException("验证码输入错误");
    		}
    		
    		//调用Service校验用户账号和密码的正确性
    		ActiveUser activeUser = sysService.authenticat(usercode, password);
    		
    		//如果Service校验通过,将用户身份记录到Session
    		session.setAttribute("activeUser", activeUser);
    		//重定向到商品查询页面
    		return "redirect:/first.action";
    	}

    用户认证拦截器

    anonymousURL.properties配置匿名URL

    配置可以匿名访问的URL


    编写身份认证拦截器

    //用于用户认证校验、用户权限校验
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            
    		//得到请求的url
    		String url = request.getRequestURI();
    		
    		//判断是否是公开地址
    		//实际开发中需要将公开地址配置在配置文件中
    		//从配置文件中取出可以匿名访问的URL
    		List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");
    		for (String open_url : open_urls) {
    			if (url.indexOf(open_url)>=0) {
    				//如果是公开地址 则放行
    				return true;
    			}
    		}
    		
    		//判断用户身份在Session中是否存在
    		HttpSession session = request.getSession();
    		ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
    		//如果用户身份在session中存在则放行
    		if (activeUser!=null) {
    			return true;
    		}
    		//执行到这里拦截,跳转到登录页面,用户进行身份认证
    		request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
    		
    		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
    		return false;
    	}

    配置认证拦截器

    <!-- 拦截器 -->
    	<mvc:interceptors>
    	    <mvc:interceptor>
    		   	<!-- 用户认证拦截 -->
    		   	<mvc:mapping path="/**"/>
    		   	<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
    	    </mvc:interceptor>
    	</mvc:interceptors>

    用户授权

    commonURL.properties配置公用访问地址

    在此配置文件中配置公用访问地址,公用访问地址只需要通过用户认证,不需要对公用访问地址分配权限即可访问。

    获取用户权限范围的菜单

    思路:
    在用户认证时,认证通过,根据用户id从数据库获取用户权限范围内的菜单,将菜单的集合存储在Session中。
    编辑存储用户身份信息的类ActiveUser 如下所示:
    public class ActiveUser implements Serializable {
    	private String userid; //用户id(主键)
    	private String usercode; // 用户账号
    	private String username; // 用户姓名
    	
    	private List<SysPermission> menus; //菜单
            //......setter和getter方法
    }
    自定义权限Mapper
    因为使用逆向工程生成的Mapper是不建议去修改的 因为它的代码联系非常紧密,一旦修改错误 就会牵一发而动全身。所以需要自定义一个权限的Mapper(SysPermissionMapperCustom)
    SysPermissionMapperCustom.xml中添加根据用户id查询用户权限的菜单
    <!-- 根据用户id查询菜单 -->
    <select id="findMenuListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">
       SELECT 
    	  * 
    	FROM
    	  sys_permission 
    	WHERE TYPE = 'menu' 
    	  AND id IN 
    	  (SELECT 
    	    sys_permission_id 
    	  FROM
    	    sys_role_permission 
    	  WHERE sys_role_id IN 
    	    (SELECT 
    	      sys_role_id 
    	    FROM
    	      sys_user_role 
    	    WHERE sys_user_id = #{userid}))
    </select>
    SysPermissionMapperCustom.java接口中添加对应的方法
    public interface SysPermissionMapperCustom {
        //根据用户id查询菜单
    	public List<SysPermission> findMenuListByUserId(String userid) throws Exception;
      .......
    }
    在权限Service接口中添加对应的方法 在实现中注入SysPermissionMapperCustom
    SysServiceImpl.java中添加如下内容
    @Override
    	public List<SysPermission> findMenuListByUserId(String userid) throws Exception {
    		return sysPermissionMapperCustom.findMenuListByUserId(userid);
    	}

    获取用户权限范围的URL

    思路:
    在用户认证时,认证通过后,根据用户id从数据库中获取用户权限范围的URL,将URL的集合存储在Session中。
    修改ActiveUser 添加URL的权限集合
    public class ActiveUser implements Serializable {
    	private String userid; //用户id(主键)
    	private String usercode; // 用户账号
    	private String username; // 用户姓名
    	
    	private List<SysPermission> menus; //菜单
    	private List<SysPermission> permissions; //权限
    	//...setter和getter方法
    }
    SysPermissionMapperCustom.xml中添加根据用户id查询用户权限的URL
    <!-- 根据用户id查询URL -->
    <select id="findPermissionListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">
       SELECT 
    	  * 
    	FROM
    	  sys_permission 
    	WHERE TYPE = 'permission' 
    	  AND id IN 
    	  (SELECT 
    	    sys_permission_id 
    	  FROM
    	    sys_role_permission 
    	  WHERE sys_role_id IN 
    	    (SELECT 
    	      sys_role_id 
    	    FROM
    	      sys_user_role 
    	    WHERE sys_user_id = #{userid}))
    </select>
    SysPermissionMapperCustom.java接口中添加对应的方法
    //根据用户id查询权限URL
    	public List<SysPermission> findPermissionListByUserId(String userid) throws Exception;
    SysServiceImpl.java中添加如下内容
    @Override
    	public List<SysPermission> findPermissionListByUserId(String userid) throws Exception {
    		return sysPermissionMapperCustom.findPermissionListByUserId(userid);
    	}

    用户认证通过后取出菜单和URL放入Session

    修改权限SysServiceImpl中用户认证方法的代码
    //得到用户id
    		String userid = sysUser.getId();
    		//根据用户id查询菜单
    		List<SysPermission> menus = this.findMenuListByUserId(userid);
    		//根据用户id查询权限url
    		List<SysPermission> permissions = this.findPermissionListByUserId(userid);
    		
    		//认证通过,返回用户身份信息
    		ActiveUser activeUser = new ActiveUser();
    		activeUser.setUserid(userid);
    		activeUser.setUsercode(usercode);
    		activeUser.setUsername(sysUser.getUsername());
    		
            //放入权限范围的菜单和url
    		activeUser.setMenus(menus);
    		activeUser.setPermissions(permissions);

    菜单动态显示

    <c:if test="${activeUser.menus!=null }">
    				<ul>
    				<c:forEach items="${activeUser.menus }" var="menu">
    					<li><div>
    						<a title="${menu.name }" ref="1_1" href="#"
    							rel="${baseurl }/${menu.url }" icon="icon-log"><span
    							class="icon icon-log"> </span><span class="nav"><a href=javascript:addTab('${menu.name }','${baseurl }/${menu.url }')>${menu.name }</a></span></a>
    					</div></li>
    				</c:forEach>
    				</ul>
    			</c:if>

    授权拦截器

    public class PermissionInterceptor implements HandlerInterceptor{
    	//在执行handler之前执行的
    	//用于用户认证校验、用户权限校验
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            
    		//得到请求的url
    		String url = request.getRequestURI();
    		
    		//判断是否是公开地址
    		//实际开发中需要将公开地址配置在配置文件中
    		//从配置文件中取出可以匿名访问的URL
    		List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");
    		for (String open_url : open_urls) {
    			if (url.indexOf(open_url)>=0) {
    				//如果是公开地址 则放行
    				return true;
    			}
    		}
    		
    		//从配置文件中获取公用访问url
    		List<String> common_urls = ResourcesUtil.getKeyList("commonURL");
    		//遍历公用地址 如果是公开地址则放行
    		for (String common_url : common_urls) {
    			if (url.indexOf(common_url)>0) {
    				//如果是公开,则放行
    				return true;
    			}
    		}
    		
    		//判断用户身份在Session中是否存在
    		HttpSession session = request.getSession();
    		ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
    		//从Session中取出权限范围的URL
    		List<SysPermission> permissions = activeUser.getPermissions();
    		for (SysPermission sysPermission : permissions) {
    			//权限url
    			String permission_url = sysPermission.getUrl();
    			if (url.indexOf(permission_url)>0) {
    				return true;
    			}
    		}
    		
    		//执行到这里拦截,跳转到无权访问的提示页面
    		request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response);
    		
    		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
    		return false;
    	}
       ......
    }

    配置授权拦截器

    注意:要将授权拦截器配置在用户认证拦截器的下边,这是因为SpringMVC拦截器的放行方法是顺序执行的,如果是Struts的话则正好相反。
    <!-- 拦截器 -->
    	<mvc:interceptors>
    	    <mvc:interceptor>
    		   	<!-- 用户认证拦截 -->
    		   	<mvc:mapping path="/**"/>
    		   	<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
    	    </mvc:interceptor>
    	    <mvc:interceptor>
    	    	<!-- 资源拦截 -->
    	    	<mvc:mapping path="/**"/>
    	    	<bean class="liuxun.ssm.controller.interceptor.PermissionInterceptor"></bean>
    	    </mvc:interceptor>
    	</mvc:interceptors>
    运行测试:
    其关键代码如下:
    PO类ActiveUser.java 存放用户身份和权限信息的类
    package liuxun.ssm.po;
    
    import java.io.Serializable;
    import java.util.List;
    
    /**
     * 用户身份信息,存入Session  由于Tomcat正常关闭时会将Session序列化的本地硬盘上,所以实现Serializable接口
     * @author liuxun
     *
     */
    public class ActiveUser implements Serializable {
    	private static final long serialVersionUID = 1L;
    	
    	private String userid; //用户id(主键)
    	private String usercode; // 用户账号
    	private String username; // 用户姓名
    	
    	private List<SysPermission> menus; //菜单
    	private List<SysPermission> permissions; //权限
        // 提供对应setter和getter方法
        ......
    }
    自定义权限的Mapper 
    SysPermissionMapperCustom.java
    package liuxun.ssm.mapper;
    
    import java.util.List;
    import liuxun.ssm.po.SysPermission;
    import liuxun.ssm.po.SysPermissionExample;
    import org.apache.ibatis.annotations.Param;
    /**
     * 权限mapper
     * @author liuxun
     *
     */
    public interface SysPermissionMapperCustom {
        //根据用户id查询菜单
    	public List<SysPermission> findMenuListByUserId(String userid) throws Exception;
    	//根据用户id查询权限URL
    	public List<SysPermission> findPermissionListByUserId(String userid) throws Exception;
    }
    SysPermissionMapperCustom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="liuxun.ssm.mapper.SysPermissionMapperCustom">
    
    <!-- 根据用户id查询菜单 -->
    <select id="findMenuListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">
       SELECT 
    	  * 
    	FROM
    	  sys_permission 
    	WHERE TYPE = 'menu' 
    	  AND id IN 
    	  (SELECT 
    	    sys_permission_id 
    	  FROM
    	    sys_role_permission 
    	  WHERE sys_role_id IN 
    	    (SELECT 
    	      sys_role_id 
    	    FROM
    	      sys_user_role 
    	    WHERE sys_user_id = #{userid}))
    </select>
    <!-- 根据用户id查询URL -->
    <select id="findPermissionListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">
       SELECT 
    	  * 
    	FROM
    	  sys_permission 
    	WHERE TYPE = 'permission' 
    	  AND id IN 
    	  (SELECT 
    	    sys_permission_id 
    	  FROM
    	    sys_role_permission 
    	  WHERE sys_role_id IN 
    	    (SELECT 
    	      sys_role_id 
    	    FROM
    	      sys_user_role 
    	    WHERE sys_user_id = #{userid}))
    </select>
    </mapper>
    自定义权限的Service接口以及实现类
    SysService.java
    package liuxun.ssm.service;
    
    import java.util.List;
    
    import liuxun.ssm.po.ActiveUser;
    import liuxun.ssm.po.SysPermission;
    import liuxun.ssm.po.SysUser;
    
    /**
     * 认证授权服务接口
     * @author liuxun
     *
     */
    public interface SysService {
    	//根据用户的身份和密码进行认证,如果认证通过,返回用户身份信息
    	public ActiveUser authenticat(String usercode,String password) throws Exception;
    	
    	//根据用户账号查询用户信息
    	public SysUser findSysUserByUserCode(String userCode) throws Exception;
    	
    	//根据用户id查询权限范围内的菜单
    	public List<SysPermission> findMenuListByUserId(String userid) throws Exception;
    	
    	//根据用户id查询权限范围内的url
    	public List<SysPermission> findPermissionListByUserId(String userid) throws Exception;
    }
    
    SysServiceImpl.java
    package liuxun.ssm.service.impl;
    
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    import liuxun.ssm.exception.CustomException;
    import liuxun.ssm.mapper.SysPermissionMapperCustom;
    import liuxun.ssm.mapper.SysUserMapper;
    import liuxun.ssm.po.ActiveUser;
    import liuxun.ssm.po.SysPermission;
    import liuxun.ssm.po.SysUser;
    import liuxun.ssm.po.SysUserExample;
    import liuxun.ssm.service.SysService;
    import liuxun.ssm.util.MD5;
    
    public class SysServiceImpl implements SysService {
    	@Autowired
    	private SysUserMapper sysUserMapper;
    	
    	@Autowired
    	private SysPermissionMapperCustom sysPermissionMapperCustom;
    
    	public ActiveUser authenticat(String usercode, String password) throws Exception {
    
    		/**
    		 * 认证过程: 根据用户身份(账号)查询数据库,如果查询不到则用户不存在 
    		 * 对输入的密码和数据库密码进行比对,如果一致则认证通过
    		 */
    		// 根据用户账号查询数据库
    		SysUser sysUser = this.findSysUserByUserCode(usercode);
    
    		if (sysUser == null) {
    			// 抛出异常
    			throw new CustomException("用户账号不存在");
    		}
    
    		// 数据库密码(MD5加密后的密码)
    		String password_db = sysUser.getPassword();
            
    		// 对输入的密码和数据库密码进行比对,如果一致,认证通过
    		// 对页面输入的密码进行MD5加密
    		String password_input_md5 = new MD5().getMD5ofStr(password);
    		if (!password_db.equalsIgnoreCase(password_input_md5)) {
    			//抛出异常
    			throw new CustomException("用户名或密码错误");
    		}
    		//得到用户id
    		String userid = sysUser.getId();
    		//根据用户id查询菜单
    		List<SysPermission> menus = this.findMenuListByUserId(userid);
    		//根据用户id查询权限url
    		List<SysPermission> permissions = this.findPermissionListByUserId(userid);
    		
    		//认证通过,返回用户身份信息
    		ActiveUser activeUser = new ActiveUser();
    		activeUser.setUserid(userid);
    		activeUser.setUsercode(usercode);
    		activeUser.setUsername(sysUser.getUsername());
    		
            //放入权限范围的菜单和url
    		activeUser.setMenus(menus);
    		activeUser.setPermissions(permissions);
    		
    		return activeUser;
    	}
    
    	public SysUser findSysUserByUserCode(String userCode) throws Exception {
    		SysUserExample sysUserExample = new SysUserExample();
    		SysUserExample.Criteria criteria = sysUserExample.createCriteria();
    		criteria.andUsercodeEqualTo(userCode);
    
    		List<SysUser> list = sysUserMapper.selectByExample(sysUserExample);
    		if (list != null && list.size() > 0) {
    			return list.get(0);
    		}
    
    		return null;
    	}
    	
    	@Override
    	public List<SysPermission> findMenuListByUserId(String userid) throws Exception {
    		return sysPermissionMapperCustom.findMenuListByUserId(userid);
    	}
    
    	@Override
    	public List<SysPermission> findPermissionListByUserId(String userid) throws Exception {
    		return sysPermissionMapperCustom.findPermissionListByUserId(userid);
    	}
    }
    登录控制器
    package liuxun.ssm.controller;
    
    import javax.servlet.http.HttpSession;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import liuxun.ssm.exception.CustomException;
    import liuxun.ssm.po.ActiveUser;
    import liuxun.ssm.service.SysService;
    
    /**
     * 登录和退出
     * @author liuxun
     *
     */
    @Controller
    public class LoginController {
        @Autowired
        private SysService sysService;
    	
    	//用户登录提交方法
    	@RequestMapping("/login")
    	public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{
    		// 校验验证码,防止恶性攻击
    		// 从Session中获取正确的验证码
    		String validateCode = (String) session.getAttribute("validateCode");
    		
    		//输入的验证码和Session中的验证码进行对比
    		if (!randomcode.equalsIgnoreCase(validateCode)) {
    			//抛出异常
    			throw new CustomException("验证码输入错误");
    		}
    		
    		//调用Service校验用户账号和密码的正确性
    		ActiveUser activeUser = sysService.authenticat(usercode, password);
    		
    		//如果Service校验通过,将用户身份记录到Session
    		session.setAttribute("activeUser", activeUser);
    		//重定向到商品查询页面
    		return "redirect:/first.action";
    	}
    	
    	//用户退出
    	@RequestMapping("/logout")
    	public String logout(HttpSession session) throws Exception{
    		//session失效
    		session.invalidate();
    		//重定向到商品查询页面
    		return "redirect:/first.action";
    	}
    }
    身份认证拦截器LoginInterceptor.java
    package liuxun.ssm.controller.interceptor;
    
    import java.util.List;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import liuxun.ssm.po.ActiveUser;
    import liuxun.ssm.util.ResourcesUtil;
    
    /**
     * 测试拦截器1
     * @author liuxun
     *
     */
    public class LoginInterceptor implements HandlerInterceptor{
    	//在执行handler之前执行的
    	//用于用户认证校验、用户权限校验
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            
    		//得到请求的url
    		String url = request.getRequestURI();
    		
    		//判断是否是公开地址
    		//实际开发中需要将公开地址配置在配置文件中
    		//从配置文件中取出可以匿名访问的URL
    		List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");
    		for (String open_url : open_urls) {
    			if (url.indexOf(open_url)>=0) {
    				//如果是公开地址 则放行
    				return true;
    			}
    		}
    		
    		//判断用户身份在Session中是否存在
    		HttpSession session = request.getSession();
    		ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
    		//如果用户身份在session中存在则放行
    		if (activeUser!=null) {
    			return true;
    		}
    		//执行到这里拦截,跳转到登录页面,用户进行身份认证
    		request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
    		
    		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
    		return false;
    	}
    
    	//在执行handler返回modelAndView之前执行
    	//如果需要向页面提供一些公用的数据或配置一些视图信息,使用此方法实现 从modelAndView入手
    	@Override
    	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    			throws Exception {
    		System.out.println("HandlerInterceptor2...postHandle");
    	}
    
    	//执行handler之后执行此方法
    	//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长
    	//实现系统,统一日志记录
    	@Override
    	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
    			throws Exception {
    		System.out.println("HandlerInterceptor2...afterCompletion");
    	}
    
    }
    资源授权拦截器PermissionInterceptor
    package liuxun.ssm.controller.interceptor;
    
    import java.security.acl.Permission;
    import java.util.List;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import liuxun.ssm.po.ActiveUser;
    import liuxun.ssm.po.SysPermission;
    import liuxun.ssm.util.ResourcesUtil;
    
    /**
     * 授权拦截器
     * @author liuxun
     *
     */
    public class PermissionInterceptor implements HandlerInterceptor{
    	//在执行handler之前执行的
    	//用于用户认证校验、用户权限校验
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            
    		//得到请求的url
    		String url = request.getRequestURI();
    		
    		//判断是否是公开地址
    		//实际开发中需要将公开地址配置在配置文件中
    		//从配置文件中取出可以匿名访问的URL
    		List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");
    		for (String open_url : open_urls) {
    			if (url.indexOf(open_url)>=0) {
    				//如果是公开地址 则放行
    				return true;
    			}
    		}
    		
    		//从配置文件中获取公用访问url
    		List<String> common_urls = ResourcesUtil.getKeyList("commonURL");
    		//遍历公用地址 如果是公开地址则放行
    		for (String common_url : common_urls) {
    			if (url.indexOf(common_url)>0) {
    				//如果是公开,则放行
    				return true;
    			}
    		}
    		
    		//判断用户身份在Session中是否存在
    		HttpSession session = request.getSession();
    		ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
    		//从Session中取出权限范围的URL
    		List<SysPermission> permissions = activeUser.getPermissions();
    		for (SysPermission sysPermission : permissions) {
    			//权限url
    			String permission_url = sysPermission.getUrl();
    			if (url.indexOf(permission_url)>0) {
    				return true;
    			}
    		}
    		
    		//执行到这里拦截,跳转到无权访问的提示页面
    		request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response);
    		
    		//如果返回false表示拦截器不继续执行handler,如果返回true表示放行
    		return false;
    	}
    
    	//在执行handler返回modelAndView之前执行
    	//如果需要向页面提供一些公用的数据或配置一些视图信息,使用此方法实现 从modelAndView入手
    	@Override
    	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    			throws Exception {
    		System.out.println("HandlerInterceptor2...postHandle");
    	}
    
    	//执行handler之后执行此方法
    	//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长
    	//实现系统,统一日志记录
    	@Override
    	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
    			throws Exception {
    		System.out.println("HandlerInterceptor2...afterCompletion");
    	}
    
    }
    拦截器配置
    <!-- 拦截器 -->
    	<mvc:interceptors>
    	    <mvc:interceptor>
    		   	<!-- 用户认证拦截 -->
    		   	<mvc:mapping path="/**"/>
    		   	<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
    	    </mvc:interceptor>
    	    <mvc:interceptor>
    	    	<!-- 资源拦截 -->
    	    	<mvc:mapping path="/**"/>
    	    	<bean class="liuxun.ssm.controller.interceptor.PermissionInterceptor"></bean>
    	    </mvc:interceptor>
    	</mvc:interceptors>
    使用URL拦截总结:
    使用基于URL拦截的权限管理方式,实现起来比较简单,不依赖框架使用过滤器或拦截器就可以实现
    弊端:需要将所有的URL全部配置起来,比较繁琐,不易维护,URL(资源)和权限表示方式不规范

    展开全文
  • Android 6.0 运行时权限管理最佳实践

    万次阅读 多人点赞 2016-09-11 13:11:36
    在Android M中权限系统被重新设计,发生了颠覆性的变化,很多人把握不好这个变化,一是对这个权限策略和套路还没有摸透,二是没有一个很好的实践来支撑,很多人问我关于权限管理的问题,往往我都没有直接回答,因为...

    版权声明:转载必须注明本文转自严振杰的博客: http://blog.yanzhenjie.com

    这是一篇迟来的博客,Android M已经发布一年多了(6.0的变化),在Android M中权限系统被重新设计,发生了颠覆性的变化,很多人把握不好这个变化,一是对这个权限策略和套路还没有摸透,二是没有一个很好的实践来支撑,在我的技术开发群里很多人问我关于权限的问题,往往我都没有直接回答,因为这个问题不是一两句说的清楚的,这几点是今天我写这篇博客的原因。这里有一切关于Android运行时权限你需要知道的,包括如何在代码中实现,如果你以前不知道这些东西,现在来看也为时不晚,我将在详解之后给你一个最佳的实践方案。

    由于项目一直在更新,更多新内容请直接看AndPermission开源主页:
    https://github.com/yanzhenjie/AndPermission

    AndPermission能解决大部分国产机遇到的权限问题,请参考:国产手机权限适配方案

    AndPermission特性

    1. 链式调用,一句话申请权限,为你省去复杂的逻辑判断。
    2. 支持注解回调结果、支持Listener回调结果。
    3. 拒绝一次某权限后,再次申请该权限时可使用Rationale向用户说明申请该权限的目的,在用户同意后再继续申请,避免用户勾选不再提示而导致不能再次申请该权限。
    4. 就算用户拒绝权限并勾选不再提示,可使用SettingDialog提示用户去设置中授权。
    5. RationaleDialogSettingDialog允许开发者自定义。
    6. AndPermission自带默认对话框除可自定义外,也支持国际化。
    7. 支持在任何地方申请权限,不仅限于ActivityFragment等。

    如果你的英文够好,推荐你阅读官网的文章:


    关于运行时权限

    在旧的权限管理系统中,权限仅仅在App安装时询问用户一次,用户同意了这些权限App才能被安装(某些深度定制系统另说),App一旦安装后就可以偷偷的做一些不为人知的事情了。

    在Android6.0开始,App可以直接安装,App在运行时一个一个询问用户授予权限,系统会弹出一个对话框让用户选择是否授权某个权限给App(这个Dialog不能由开发者定制),当App需要用户授予不恰当的权限的时候,用户可以拒绝,用户也可以在设置页面对每个App的权限进行管理。

    特别注意:这个对话框不是开发者调用某个权限的功能时由系统自动弹出,而是需要开发者手动调用,如果你直接调用而没有去申请权限的话,将会导致App崩溃。

    也许你已经开始慌了,这对于用户来说是好事,但是对于开发者来说我们不能直接调用方法了,我们不得不在每一个需要权限的地方检查并请求用户授权,所以就引出了以下两个问题。

    哪些权限需要动态申请

    新的权限策略讲权限分为两类,第一类是不涉及用户隐私的,只需要在Manifest中声明即可,比如网络、蓝牙、NFC等;第二类是涉及到用户隐私信息的,需要用户授权后才可使用,比如SD卡读写、联系人、短信读写等。

    不需要运行时申请的权限

    此类权限都是正常保护的权限,只需要在AndroidManifest.xml中简单声明这些权限即可,安装即授权,不需要每次使用时都检查权限,而且用户不能取消以上授权,除非用户卸载App。

    • ACCESS_LOCATION_EXTRA_COMMANDS
    • ACCESS_NETWORK_STATE
    • ACCESS_NOTIFICATION_POLICY
    • ACCESS_WIFI_STATE
    • BLUETOOTH
    • BLUETOOTH_ADMIN
    • BROADCAST_STICKY
    • CHANGE_NETWORK_STATE
    • CHANGE_WIFI_MULTICAST_STATE
    • CHANGE_WIFI_STATE
    • DISABLE_KEYGUARD
    • EXPAND_STATUS_BAR
    • GET_PACKAGE_SIZE
    • INSTALL_SHORTCUT
    • INTERNET
    • KILL_BACKGROUND_PROCESSES
    • MODIFY_AUDIO_SETTINGS
    • NFC
    • READ_SYNC_SETTINGS
    • READ_SYNC_STATS
    • RECEIVE_BOOT_COMPLETED
    • REORDER_TASKS
    • REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
    • REQUEST_INSTALL_PACKAGES
    • SET_ALARM
    • SET_TIME_ZONE
    • SET_WALLPAPER
    • SET_WALLPAPER_HINTS
    • TRANSMIT_IR
    • UNINSTALL_SHORTCUT
    • USE_FINGERPRINT
    • VIBRATE
    • WAKE_LOCK
    • WRITE_SYNC_SETTINGS

    需要运行时申请的权限

    所有危险的Android系统权限属于权限组,如果APP运行在Android 6.0 (API level 23)或者更高级别的设备中,而且targetSdkVersion>=23时,系统将会自动采用动态权限管理策略,如果你在涉及到特殊权限操作时没有申请权限权限而直接调用了相关代码,你的App可能就崩溃了,综上所述你需要注意:

    • 此类权限也必须在Manifest中申明,否则申请时不提示用户,直接回调开发者权限被拒绝。
    • 同一个权限组的任何一个权限被授权了,这个权限组的其他权限也自动被授权。例如一旦WRITE_CONTACTS被授权了,App也有READ_CONTACTSGET_ACCOUNTS了。
    • 申请某一个权限的时候系统弹出的Dialog是对整个权限组的说明,而不是单个权限。例如我申请READ_EXTERNAL_STORAGE,系统会提示"允许xxx访问设备上的照片、媒体内容和文件吗?"

    如果App运行在Android 5.1 (API level 22)或者更低级别的设备中,或者targetSdkVersion<=22时(此时设备可以是Android 6.0 (API level 23)或者更高),在所有系统中仍将采用旧的权限管理策略,系统会要求用户在安装的时候授予权限。其次,系统就告诉用户App需要什么权限组,而不是个别的某个权限。

    • CALENDAR(日历)
      • READ_CALENDAR
      • WRITE_CALENDAR
    • CAMERA(相机)
      • CAMERA
    • CONTACTS(联系人)
      • READ_CONTACTS
      • WRITE_CONTACTS
      • GET_ACCOUNTS
    • LOCATION(位置)
      • ACCESS_FINE_LOCATION
      • ACCESS_COARSE_LOCATION
    • MICROPHONE(麦克风)
      • RECORD_AUDIO
    • PHONE(手机)
      • READ_PHONE_STATE
      • CALL_PHONE
      • READ_CALL_LOG
      • WRITE_CALL_LOG
      • ADD_VOICEMAIL
      • USE_SIP
      • PROCESS_OUTGOING_CALLS
    • SENSORS(传感器)
      • BODY_SENSORS
    • SMS(短信)
      • SEND_SMS
      • RECEIVE_SMS
      • READ_SMS
      • RECEIVE_WAP_PUSH
      • RECEIVE_MMS
    • STORAGE(存储卡)
      • READ_EXTERNAL_STORAGE
      • WRITE_EXTERNAL_STORAGE

    使用adb命令可以查看这些需要授权的权限组:

    adb shell pm list permissions -d -g

    使用adb命令同样可以授权/撤销某个权限:

    adb shell pm [grant|revoke] <permission-name>...

    关于运行时权限的一些建议

    1. 只请求你需要的权限,减少请求的次数,或用隐式Intent来让其他的应用来处理。

      1. 如果你使用Intent,你不需要设计界面,由第三方的应用来完成所有操作。比如打电话、选择图片等。
      2. 如果你请求权限,你可以完全控制用户体验,自己定义UI。但是用户也可以拒绝权限,就意味着你的应用不能执行这个特殊操作。
    2. 防止一次请求太多的权限或请求次数太多,用户可能对你的应用感到厌烦,在应用启动的时候,最好先请求应用必须的一些权限,非必须权限在使用的时候才请求,建议整理并按照上述分类管理自己的权限:

      1. 普通权限(Normal PNermissions):只需要在Androidmanifest.xml中声明相应的权限,安装即许可。
      2. 需要运行时申请的权限(Dangerous Permissions):
        • 必要权限:最好在应用启动的时候,进行请求许可的一些权限(主要是应用中主要功能需要的权限)。
        • 附带权限:不是应用主要功能需要的权限(如:选择图片时,需要读取SD卡权限)。
    3. 解释你的应用为什么需要这些权限:在你调用requestPermissions()之前,你为什么需要这个权限。

      1. 例如,一个摄影的App可能需要使用定位服务,因为它需要用位置标记照片。一般的用户可能会不理解,他们会困惑为什么他们的App想要知道他的位置。所以在这种情况下,所以你需要在requestpermissions()之前告诉用户你为什么需要这个权限。
    4. 使用兼容库support-v4中的方法

    ContextCompat.checkSelfPermission()
    ActivityCompat.requestPermissions()
    ActivityCompat.shouldShowRequestPermissionRationale()

    几个重要的方法与常量解释

    • PackageManager中的两个常量:

      • PackageManager.PERMISSION_DENIED:该权限是被拒绝的。
      • PackageManager.PERMISSION_GRANTED:该权限是被授权的。
    • Activity中或者Fragment都会有以下几个方法:

    int checkSelfPermission(String)
    void requestPermissions(int, String...)
    boolean shouldShowRequestPermissionRationale(String)
    void onRequestPermissionsResult()

    上述四个方法中,前三个方法在support-v4ActivityCompat中都有,建议使用兼容库中的方法。最后一个方法是用户授权或者拒绝某个权限组时系统会回调Activity或者Fragment中的方法。

    checkSelfPermission() 检查权限

    1. 检查某一个权限的当前状态,你应该在请求某个权限时检查这个权限是否已经被用户授权,已经授权的权限重复申请可能会让用户产生厌烦。
    2. 该方法有一个参数是权限名称,有一个int的返回值,用这个值与上面提到的两个常量做比较可判断检查的权限当前的状态。
    if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED) {
        // 没有权限,申请权限。
    }else{
        // 有权限了,去放肆吧。
    }

    requestPermissions() 申请权限

    1. 请求用户授权几个权限,调用后系统会显示一个请求用户授权的提示对话框,App不能配置和修改这个对话框,如果需要提示用户这个权限相关的信息或说明,需要在调用 requestPermissions() 之前处理,该方法有两个参数:
      • int requestCode,会在回调onRequestPermissionsResult()时返回,用来判断是哪个授权申请的回调。
      • String[] permissions,权限数组,你需要申请的的权限的数组。
    2. 由于该方法是异步的,所以无返回值,当用户处理完授权操作时,会回调Activity或者Fragment的onRequestPermissionsResult()方法。

    对于Activity我们直接调用requestPermissions(int, String[])即可,不过这个方法是在api leve 23以上,所以我们为了适配可以是使用兼容包提供的方法:

    ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);

    对于support包的Fragment就可以直接调用requestPermissions(int, String[]),对于app包的Fragment就需要做版本判断了,这样就显得比较麻烦。

    onRequestPermissionsResult() 处理权限结果回调

    1. 该方法在Activity/Fragment中应该被重写,当用户处理完授权操作时,系统会自动回调该方法,该方法有三个参数:
      • int requestCode,在调用requestPermissions()时的第一个参数。
      • String[] permissions,权限数组,在调用requestPermissions()时的第二个参数。
      • int[] grantResults,授权结果数组,对应permissions,具体值和上方提到的PackageManager中的两个常量做比较。
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MMM: {
                if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 权限被用户同意,可以去放肆了。
                } else {
                    // 权限被用户拒绝了,洗洗睡吧。
                }
                return;
            }
        }
    }

    shouldShowRequestPermissionRationale()

    1. 望文生义,是否应该显示请求权限的说明。
    2. 第一次请求权限时,用户拒绝了,调用shouldShowRequestPermissionRationale()后返回true,应该显示一些为什么需要这个权限的说明。
    3. 用户在第一次拒绝某个权限后,下次再次申请时,授权的dialog中将会出现“不再提醒”选项,一旦选中勾选了,那么下次申请将不会提示用户。
    4. 第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项,调用shouldShowRequestPermissionRationale()后返回false。
    5. 设备的策略禁止当前应用获取这个权限的授权:shouldShowRequestPermissionRationale()返回false 。
    6. 加这个提醒的好处在于,用户拒绝过一次权限后我们再次申请时可以提醒该权限的重要性,免得再次申请时用户勾选“不再提醒”并决绝,导致下次申请权限直接失败。

    综上所述,整合代码后:

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
        // 没有权限。
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
                // 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。
        } else {
            // 申请授权。
            ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);
        }
    }
    
    ...
    
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MMM: {
                if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 权限被用户同意,可以去放肆了。
                } else {
                    // 权限被用户拒绝了,洗洗睡吧。
                }
                return;
            }
        }
    }

    总结

    从上面来看,判断很多,逻辑也很多,这样就加重了我们开发的负担,加上很多人反馈说国产手机有各种各样的bug,这样兼容起来就更加麻烦了,那么下面我就为大家介绍一个开源内裤来解决这一系列问题。

    AndPermission

    这个开源库名叫AndPermission:https://github.com/yanzhenjie/AndPermission,经过我的实践是完全解决了上述问题,推荐大家使用。

    • Gradle
    compile 'com.yanzhenjie:permission:1.0.6'
    • Maven
    <dependency>
      <groupId>com.yanzhenjie</groupId>
      <artifactId>permission</artifactId>
      <version>1.0.5</version>
      <type>pom</type>
    </dependency>
    • Eclipse 请放弃治疗

    使用介绍

    我建议看官去Github下载Demo并阅读本文会帮助你理解。

    申请权限

    // 在Activity:
    AndPermission.with(activity)
        .requestCode(100)
        .permission(Manifest.permission.WRITE_CONTACTS)
        .rationale(...)
        .callback(...)
        .start();
    
    // 在Fragment:
    AndPermission.with(fragment)
        .requestCode(100)
        .permission(
            // 多个权限,以数组的形式传入。
            Manifest.permission.WRITE_CONTACTS,
            Manifest.permission.READ_SMS
        )
        .rationale(...)
        .callback(...)
        .start();
    
    // 在其它任何地方:
    AndPermission.with(context)
        .requestCode(100)
        .permission(
            Manifest.permission.WRITE_CONTACTS,
            Manifest.permission.READ_SMS
        )
        .rationale(...)
        .callback(...)
        .start();

    接受回调结果

    接受回调结果目前有两种方式:一、Listener方式,二、注解方式。

    方式一:Listener方式回调

    callback()方法传入PermissionListener即可,授权成功或者失败至少会回调其中一个方法。

    AndPermission.with(context)
        ...
        .requestCode(200)
        .callback(listener)
        .start();
    
    private PermissionListener listener = new PermissionListener() {
        @Override
        public void onSucceed(int requestCode, List<String> grantedPermissions) {
            // 权限申请成功回调。
    
            // 这里的requestCode就是申请时设置的requestCode。
            // 和onActivityResult()的requestCode一样,用来区分多个不同的请求。
            if(requestCode == 200) {
                // TODO ...
            }
        }
    
        @Override
        public void onFailed(int requestCode, List<String> deniedPermissions) {
            // 权限申请失败回调。
            if(requestCode == 200) {
                // TODO ...
            }
        }
    };

    方式二:注解方式回调

    callback()方法传入你的回调方法所在实例的对象即可。

    AndPermission.with(context)
        ...
        .requestCode(300)
        .callback(this)
        .start();
    
    // 成功回调的方法,用注解即可,这里的300就是请求时的requestCode。
    @PermissionYes(300)
    private void getPermissionYes(List<String> grantedPermissions) {
        // TODO 申请权限成功。
    }
    
    @PermissionNo(300)
    private void getPermissionNo(List<String> deniedPermissions) {
        // TODO 申请权限失败。
    }

    如果你会用了,你就可以大刀阔斧的干了,博客中讲到的各种复杂逻辑,AndPermission自动完成。

    Rationale能力

    Android运行时权限有一个特点,在拒绝过一次权限后,再此申请该权限,在申请框会多一个[不再提示]的复选框,当用户勾选了[不再提示]并拒绝了权限后,下次再申请该权限将直接回调申请失败。
    因此Rationale功能是在用户拒绝一次权限后,再次申请时检测到已经申请过一次该权限了,允许开发者弹窗说明申请权限的目的,获取用户的同意后再申请权限,避免用户勾选不再提示,导致不能再次申请权限。

    方式一:使用AndPermssion默认MD风格对话框

    AndPermission.with(this)
        ...
        .requestCode(...)
        .rationale((requestCode, rationale) ->
            // 此对话框可以自定义,调用rationale.resume()就可以继续申请。
            AndPermission.rationaleDialog(context, rationale).show()
        )
        .start()

    方式二:自定义对话框

    AndPermission.with(this)
        ...
        .requestCode(...)
        .rationale(rationaleListener)
        .start()
    
    /**
     * Rationale支持,这里自定义对话框。
     */
    private RationaleListener rationaleListener = (requestCode, rationale) -> {
        AlertDialog.newBuilder(this)
            .setTitle("友好提醒")
            .setMessage("你已拒绝过定位权限,沒有定位定位权限无法为你推荐附近的妹子,你看着办!")
            .setPositiveButton("好,给你", (dialog, which) -> {
                rationale.resume();
            })
            .setNegativeButton("我拒绝", (dialog, which) -> {
                rationale.cancel();
            }).show();
    };

    提示用户在系统设置中授权

    当用户拒绝权限并勾选了不再提示时,此时再次申请权限时将会直接回调申请失败,因此AndPermission提供了一个供用户在系统Setting中给我们授权的能力。

    我们在授权失败的回调方法中添加如下代码,以下三种选择一种即可:

    // 是否有不再提示并拒绝的权限。
    if (AndPermission.hasAlwaysDeniedPermission(activity, deniedPermissions)) {
        // 第一种:用AndPermission默认的提示语。
        AndPermission.defaultSettingDialog(activity, 400).show();
    
        // 第二种:用自定义的提示语。
        AndPermission.defaultSettingDialog(activity, 400)
        .setTitle("权限申请失败")
        .setMessage("您拒绝了我们必要的一些权限,已经没法愉快的玩耍了,请在设置中授权!")
        .setPositiveButton("好,去设置")
        .show();
    
        // 第三种:自定义dialog样式。
        SettingService settingService = AndPermission.defineSettingDialog(activity, 400);
        ...
        // 你的dialog点击了确定调用:
        settingService.execute();
        // 你的dialog点击了取消调用:
        settingService.cancel();
    }

    如果你是在Activity/Fragment中调用的上述代码,那么当用户在系统Setting中操作完成后,会回调Activity/Fragment中的这个方法:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case 400: { // 这个400就是你上面传入的数字。
                // 你可以在这里检查你需要的权限是否被允许,并做相应的操作。
                break;
            }
        }
    }

    混淆

    1. 如果使用Listener接受回调结果,不用任何配置。
    2. 使用注解的方式回调结果,在proguard中添加如下配置:
    -keepclassmembers class ** {
        @com.yanzhenjie.permission.PermissionYes <methods>;
    }
    -keepclassmembers class ** {
        @com.yanzhenjie.permission.PermissionNo <methods>;
    }

    国产手机适配方案

    AndPermission是严格按照Android系统的运行时权限设计的,并最大限度上兼容了国产手机,目前发现的国产手机bug及解决方案:

    • 部分中国厂商生产手机(例如小米某型号)的Rationale功能,在第一次拒绝后,第二次申请时不会返回true,并且会回调申请失败,也就是说在第一次决绝后默认勾选了不再提示,所以建议一定使用SettingDialog提示用户在系统设置中授权
    • 部分中国厂商生产手机(例如小米、华为某型号)在申请权限时,用户点击确定授权后,还是回调我们申请失败,这个时候其实我们是拥有权限的,所以我们可以在失败的方法中使用AppOpsManager进行权限判断,AndPermission已经封装好了:
    if(AndPermission(context, permission1, permission2)) {
        // 执行操作。
    }
    • 部分中国厂商生产手机(例如vivo、pppo某型号)在用户允许权限,并且回调了权限授权陈功的方法,但是实际执行代码时并没有这个权限,建议开发者在回调成功的方法中也利用AppOpsManager判断下:
    if(AndPermission(context, permission1, permission2)) {
        // 执行操作。
    } else {
        // 提醒用户手机问题,请用户去Setting中授权。这里可以使用AndPermission的SettingDialog。
    }
    • 部分开发者反馈,在某些手机的Setting中授权后实际,检查时还是没有权限,部分执行代码也是没有权限,这种手机真的兼容不到了,我也觉得没必要兼容了,建议直接放弃这种平台。

    最后希望咱中国Android手机厂商早日修复这些问题,祝你们事业越来越成功,产品越做越好。


    版权声明:转载必须注明本文转自严振杰的博客: http://blog.yanzhenjie.com

    展开全文
  • 基于Spring Security实现权限管理系统

    万次阅读 多人点赞 2018-11-06 16:49:49
    基于Spring Security实现权限管理系统 稍微复杂一点的后台系统都会涉及到用户权限管理。何谓用户权限?我的理解就是,权限就是对数据(系统的实体类)和数据可进行的操作(增删查改)的集中管理。要构建一个可用的...
  • Easyui+ashx+Jq权限菜单权限管理框架

    千次下载 热门讨论 2013-03-08 15:32:11
    Easyui+ashx+Jq权限菜单权限管理框架,前台完全html+jq+easyui 后台ashx三层 角色、权限、组织、用户管理,通过菜单控制访问权限
  • Android权限管理

    千次阅读 2019-07-17 12:39:52
    Android权限管理(PermissionsDispatcher框架使用)简述一、Android权限二、6.0以上权限管理 简述 由于对于安全考虑,Android对于权限的管理更加的严谨,以6.0位界分为两种处理方式:6.0以下系统还是保持旧的处理...
  • 权限管理框架

    千次阅读 2019-04-15 14:40:52
    之前看的视频笔记, 做个备份,以免想找的时候总找不到。... 基本上涉及到用户参与的系统都要进行权限管理权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略...
  • vue 权限管理

    千次阅读 2018-10-31 21:33:43
    今天来说说权限管理,因为网上已经有很多关于这方面的很多内容,博主也是借鉴了网上的一些逻辑来写的,主要也是说说前端实现权限管理的一个思路,也是作为自己日后面对这样的问题的一个解决方案。 首先需要了解的...
  • 后台管理系统 – 权限管理

    千次阅读 2018-11-21 14:23:35
    不管是开发手机APP,网站还是小程序等项目,基本上都需要一个后台管理系统的支撑。而每个后台管理系统都有一个通用的功能就是用户权限管理。最近基于Antd+React.js做了一个后台管理系统。
  • 权限管理-数据权限

    千次阅读 2016-05-12 11:36:56
    权限管理-数据权限
  • 权限管理在thinkphp有两种方式,比较常见用是RBAC,也是比较人用,容易理解,是基于节点的权限管理,Auth也可以做权限管理基于规则的权限管理,下面分别说说 1,Thinkphp RBAC权限管理,5张表就可以轻松搞点权限管理...
  • Rbac权限管理--如何设计

    万次阅读 多人点赞 2017-06-19 21:19:33
    RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色...项目背景: 设计一个Rbac权限管理微服务,供其他模块使用。 RBAC ServerNOTE
  • 这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。 采用jpa技术来自动生成基础表格,对应的entity如下: 用户信息 @Entity public class UserInfo ...
  • SpringSecurity实现完整的权限管理 使用技术 相关概念 数据库表设计 项目结构 相关依赖 功能部分 效果展示 案例代码下载 SpringSecurity实现完整动态权限菜单 在实际开发中,开发任何一套系统,基本都...
  • 权限管理系统

    万次阅读 热门讨论 2016-09-27 18:50:25
    菜单权限系统在大学毕业时,写了一个权限管理系统,由于经验不够丰富,所以设计出来的作品,缺点多多。经过一年多的工作,对权限管理系统进行了更加系统的整理与学习,于是重构了整个权限管理系统。该文章主要讨论...
  • shiro权限管理框架学习

    万次阅读 2019-11-08 21:32:56
    shiro权限管理框架学习 一、权限框架简介: 基本上涉及到用户参与的系统都要进行权限管理权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问...
  • Vue权限管理

    千次阅读 2018-11-28 12:17:49
    Vue的权限管理前言我们首先去定义一个路由表下面就是我们的各个组件的定义了 前言 博主最近刚刚上手一个图书管理系统的小Demo,在编写的过程中遇到了许多的问题,尤其是对于我这样的新手来说权限管理是一个难点,...
  • java权限管理系统的jar包

    千次下载 热门讨论 2012-11-10 10:42:19
    java权限管理系统的jar包配合http://www.oschina.net/code/snippet_59256_15087使用。
  • 3. 掌握基于url的权限管理(不使用权限框架的情况下实现权限管理) 权限管理原理知识 什么是权限管理 只要有用户参与的系统一般都要有权限管理权限管理实现对用户访问系统的控制。按照安全规则或安全策略控制用户...
  • java权限管理与用户角色权限设计

    万次阅读 多人点赞 2018-06-16 00:14:28
    java权限管理与用户角色权限设计2016年10月29日 19:24:41阅读数:12545实现业务系统中的用户权限管理 B/S系统中的权限比C/S中的更显的重要,C/S系统因为具有特殊的客户端,所以访问用户的权限检测可以通过客户端...
  • Salesforce 权限管理

    千次阅读 2018-01-04 17:15:50
    Salesforce 权限管理 Salesforce 对于权限的管理是非常严谨的并且支持不同维度的权限控制。常用的有Profiles(简档)、Permissions Set(权限集)、Role Hierarchy(角色层级结构)和Organization-Wide(组织范围)...
  • 设计权限管理模块

    万次阅读 2019-05-15 12:25:58
    我们比较常见的就是基于角色的访问控制,用户通过角色与权限进行关联。简单地说,一个用户拥有多个角色,一个角色拥有多个权限。这样,就构造成“用户-...例如:一个论坛的“管理员”、“版主”,它们都是角色。但是...
  • springboot+security实现权限管理

    万次阅读 多人点赞 2019-03-21 14:06:45
    /** * @Author: Galen * @Date: 2019/3/27-14:43 * @Description: spring-security权限管理的核心配置 **/ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) //全局 public class ...
  • Winform通用框架之权限管理系统

    万次阅读 多人点赞 2018-06-24 15:24:52
    Winfrom通用权限管理系统 Winfrom通用权限管理--登录 Winfrom通用权限管理--菜单 Winfrom通用权限管理系统--数据库设计 Winfrom通用权限管理----数据库问题 心血来潮,想写个通用管理系统整体界面结构采用VS 可...
  • 权限管理简介

    千次阅读 2018-05-23 18:21:34
    权限管理属于系统安全的范畴,是实现对用户访问系统的控制,可以按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 155,104
精华内容 62,041
关键字:

权限管理