权限Shiro框架怎么在项目中使用?

一、客户关系管理系统项目:

组织机构模块:   员工管理 、 部门管理  、主题设置
权限模块:       菜单管理 、权限管理 、 资源管理 、角色管理
基础数据模块:   数据字典明细 、 数据字典目录
高级业务模块:   订单管理  、合同管理  、保修管理
客户模块:       客户管理  、 潜在客户 、 客户跟进历史 、客户资源池  、潜在客户开发 、 客户移交

二、开发该项目的工具:

开发工具:  eclipse   
开发语言 : java语言     javascript语言
网页动态技术: jsp     
数据库 :    MySQL   
前台展示数据框架: EasyUI框架
采用SSM项目集成框架
Spring : 管理整个项目
Spring-Mvc : SpringMVC是一个基于MVC模式的WEB框架,它解决WEB开发中常见
的问题(参数接收、文件上传/下载、表单验证、国际化、等等),使用非常简
单,SpringMVC作为Spring中的一个模块,可以与Spring无缝集成。 前台与后台程序
技术。
Mybatis :     java代码连接数据库的一种框架技术
Shiro框架:一种安全shiro框架
Activiti:       多个人一起做一件事情的步骤,这个步骤可以让计算机理解的步骤
===========================================================
在这里给大家简单介绍一下权限模块的大体思路:
	不同的用户拥有不同的角色,不同的角色拥有不同的权限,一个权限对应了一个资源/菜单
	权限控制的内容:
		页面菜单的显示:
		页面按钮的显示:
		身份认证:
		授权:
		数据模型(重要模型):
	主要功能:
		添加员工的时候赋予所对应的角色
		添加角色的时候赋予所对应的权限
		那么员工就可以根据不同的角色访问不同的权限资源。

三、登录权限

 A 、 完成集成shiro配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
	
	<!-- 配置核心对象securityManager -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="ssmRealm"></property>
	</bean>
	
	<!-- 自定义的Realm -->
	<bean id="ssmRealm" class="cn.itsource.crm.shiro.realm.MyRealm">
		<!-- 比较器 -->
		<property name="credentialsMatcher">
			<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
				<!-- 加密的算法 -->
				<property name="hashAlgorithmName" value="MD5"></property>
				<!-- 加密的次数 -->
				<property name="hashIterations" value="1000"></property>
			</bean>
		</property>
	</bean>
	
	<!-- 过滤器 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager"/>
     <!--     登录页面 访问需要认证才能访问的资源时,如果没有认证,跳转到登录界面 -->
        <property name="loginUrl" value="/login.jsp"/>
       <!--  认证成功后的页面 -->
        <property name="successUrl" value="/main"/>
      <!--  当访问需要授权才能访问的资源时,如果没有权限,就跳转到这个页面 -->
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        
       <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
		<property name="filters">
			<map>
				<entry key="crmPerms">
					 <bean class="cn.itsource.crm.filter.CrmAccessControllerFilter"></bean>
				</entry>
			</map>
		</property>
	</bean>
	
	<!-- 工厂bean -->
	<bean id="myFactory" class="cn.itsource.crm.shiro.factory.FilterChainDefinitionMapFactoryBean"></bean>
	<bean id="filterChainDefinitionMap" factory-bean="myFactory" factory-method="getFilterChainDefinitionMap"></bean>
</beans>


B  、在apolicationContext.xml中引入shiro.xml文件

	<import resource="classpath:applicationContext-*.xml"/>



C 、 在web.xml中配置
	<!-- 
  	shiro的代理过滤器 
  	filter-name的值要和applicationContext-shiro.xml中过滤器的id一致
 	 -->
   <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
   <!--  会话超时  --> 
   <session-config>
   		<session-timeout>30</session-timeout>
   </session-config>

	数据库密码和登录密码必须保证是加密加盐加次数

3.1 登录页面login.jsp

写一个简单的登录界面
	可以做按回车键登录功能
		给登录按钮绑定keyup事件,判断event.keyCode是否等于13,如果等于。提交表单
		<script type="text/javascript">
$(document.documentElement).on("keyup", function(event) {
	//console.debug(event.keyCode);
	var keyCode = event.keyCode;
	console.debug(keyCode);
	if (keyCode === 13) { // 捕获回车 
		submitForm(); // 提交表单
	}
});
超时功能
// 检查自己是否是顶级页面
if (top != window) {// 如果不是顶级
	//把子页面的地址,赋值给顶级页面显示
	window.top.location.href = window.location.href;
}

3.2 编写remal

	自定义一个remal去继承AuthorizingRealm,实现两个方法,一个身份认证方法,授权方法。
	身份认证思路:
		从token对象获取登录用户
		再根据登录用户获取登录名
		根据登录名查询数据库返回一个用户
		再这里先判断用户是否存在
		把该用户封装到info对象中,注意:参数 加密加盐加次数
		这里其实把token对象中的用户和info对象中的用户做比较,那么就需要一个比较器,
		在上面的shiro.xml已经配置了。
	接下来写一个LoginController
		思路:
			获取当前登录用户
			判断是否被认证,如果未被认证,把用户名和密码封装到token对象中,再调用登录
			方法如果登录成功,需要把当前登录用户信息存在session中,在这里,可以写一个
			工具类(UserContext) 提供两个方法,
			1、把当前登录用户信息存入到seesio中,
			2、从session取出登录用户信息,这样做的话方便我们在任何地方调用,
			因为很多地方都需要使用它。

3.3 高级功能:

		登录用户显示:
			就是显示当前登录显示在本系统中,此时就可以再jsp页面中直接从session获取了
		用户注销:
			非常简单,直接使用shiro框架的logout就可以注销了
		回车登录:
			上面已经详解
		登录超时处理:
		上面已经详解

=====================================================

四、权限设计:

为什么需要权限?
	对于我们的系统的一些功能只有特定的角色才能访问资源,不同的用户拥有不同的角色,
	但是呢,不同的角色又拥有不同的权限,因此访问的不同用户访问系统看到的就是不同的
	资源,权限其实就是给资源加锁,
权限相关实体关系
用户== 多对多==角色==多对多==权限===资源
其实在这里权限和资源之间存在三种实体关系,一对多、一对多、多对多。
今天小编就采用了一对一给大家讲解一下。

4.1 权限数据怎么添加?

	一个要控制资源就有一个权限,那么权限数据怎么来?
	手动添加:在页面写一个添加按钮,一条一条数据添加
	动态添加:在页面写一个一键加载资源按钮,可以把所有数据添加到数据库,(采用)
思路:
自定义一个注解PermissionResource,然后标注在需要管理的资源上
	@Target(ElementType.METHOD)
	@Retention(RetentionPolicy.RUNTIME)
	public @interface PermissionResource {
		String name();
		String sn();
	}

4.2 写一个一键加资源权限方法

思路:
//一键加载资源权限路径
@RequestMapping("/load")
@ResponseBody
public AjaxResult loadResource(HttpServletRequest req){
	List<Permission> list;
	try {
		list = new ArrayList<>();
		 //扫描cn.itsource.crm.controller包下面的所有类
		 // 如果有Class.forName(""); 包名+类名
		 //获取WEB-INF/classes/cn/itsource/crm/controller包下面的所有.class文件,绝对路径
		//一会下面需要拼接起来
		String packageName="cn.itsource.crm.controller";
		 //获取绝对路径
		String contextPath = req.getRealPath("WEB-INF/classes/cn/itsource/crm/controller");
		//创建一个file
		File file = new File(contextPath);
		//遍历出该文件夹下面所有的.class文件
		String[] listClasses = file.list();
				
		for (String string : listClasses) {
			/*System.out.println(string);*/ // PermissionController.class
		}
		//获取类的完全限定名
		String [] classNames = new String[listClasses.length];
				
		for(int i=0 ; i<listClasses.length;i++ ){
			classNames[i] = packageName+"."+listClasses[i].split("\\.")[0];	
		}
				
		//所有的controller的class对象
		List<Class> classList = new ArrayList<>();
		//遍历类的完全限定名
		for (String string : classNames) {
			 Class c =Class.forName(string);
			 classList.add(c);
		}
				
		//扫描每个class上的permissionResource注解 使用循环扫描
		//封装到Permission中,一个权限封装一个Permission ,把它保存到数据库
		//先扫描类上面的requestMapping注解的值
		for (Class class1 : classList) {
					
			String classPath = "";
			//先判断是否有注解
			if(class1.isAnnotationPresent(RequestMapping.class)){
						
			//扫描 类上面的Requestmapping注解的值       sn  name 
				RequestMapping requestMapping = (RequestMapping)class1.getAnnotation(RequestMapping.class);
				classPath = requestMapping.value()[0];  //permission
			}
					
			//再继续扫描方法上的requestMapping和自己写的注解PermissionResource
			Method[] methods = class1.getMethods();
				 String methodPath = "";
			 for (Method method1 : methods) {
				 //获取方法上有自己写的PermissionResource注解的注解
				PermissionResource annotation = method1.getAnnotation(PermissionResource.class);
				//判断,如果不为空,那么就获取方法上的自定义注解
				if(annotation != null){
					  //System.out.println(method1.getName());
				RequestMapping annotation2 = method1.getAnnotation(RequestMapping.class);
					methodPath = annotation2.value()[0];
							
					//资源
					String resource =classPath + methodPath; 
					//继续获取权限名称 和标识
					String sn = annotation.sn();
					String name = annotation.name();
							
					//创建权限对象
					Permission permission = new Permission(sn , name , resource);
					//把权限对象添加到list中
					list.add(permission);
					}
				 }
					
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return new AjaxResult("加载失败"+e.getMessage());
		}
		//将list中的所有权限信息添加到数据库
		permissionService.loadPermission(list);
			
		return  new AjaxResult();
	}

4.3 Service层

接下来在service写一个loadPermission(List<Permission> list){
	方案一:
		先删除权限表
		再添加所有权限   如果这样的话思路没有问题,但是数据库中权限表的id是自增的,对应
		不上role_permission表的id,到时候找不到,无法显示。
	方案二:
		因此采用这种方案:
			不用删除权限表中的数据,如果权限表中已经加载过了,就不需要重复加载了
			加载未加载过的权限就OK了。
	@Override
	@Transactional
	public void loadPermission(List<Permission> list) {
		//查询数据库中旧的权限
		List<Permission> oldPermissions = permissionMapper.selectAll();
		//获取list中比oldPermissions多出来的权限
		//两个集合取差集
	        list.removeAll(oldPermissions);
		//添加未有的权限
		for (Permission permission : list) {
			permissionMapper.insert(permission);
		}
		还需要注意:在perimssion中需要
		
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Permission other = (Permission) obj;
			if (sn == null) {
				if (other.sn != null)
					return false;
			} else if (!sn.equals(other.sn))
					return false;
			return true;
				}
			}
		}

4.4 角色列表:

	功能:添加角色的时候并且赋予该角色的拥有的权限
	
	前台:点击添加,弹出一个模态框
	模态框中有表单和数据表格
	分为上下两部分,上是表单,下是数据表格
	数据表格分为左右两部分,左是已选择权限,右是所有权限
	当点击保存的时候,把表单中的数据个已选择权限一起添加到
	数据库,这样的话就实现添加角色的时候赋予了权限功能

4.4.1 添加、删除、修改思路

4.4.1.1 添加:

	前台:当点击保存的时候,是同时保存表单数据和数据表格数据,因为我使用的easyUi框架,
	表单中的数据可以直提交,但是呢 ,数据表格不会提交,那么需要额外参数提交就行了。
	点击添加需要先清空数据
 //清空左侧datagrid
 selectPermission.datagrid("loadData",{
	total:0,
	rows:[] 
 })
/*  alert(0) */
//添加需要先清空表单
 roleForm.form("clear");
//把dialog模态框打开、居中、设置标题
roleDialog.dialog("open").dialog("center").dialog("setTitle","添加角色");		
onSubmit: function(param){
	/获取选中的那一行
	var rows = selectPermission.datagrid("getRows")
	console.debug(rows.id);
	//传额外的参数 就是数据表单中数据
	 for(var i in rows){
		param["permissions["+i+"].id"]= rows[i].id;
	 }		
	后台:
	调用保存方法,注意:在这里先保存角色表,然后在保存中间表,那么中间表怎么保存呢,
	有两个字段id,角色id和权限id, 先从role中获取所有权限,遍历它,那么就可以得到权限的id了,
	在创建中间表对象,把角色id和权限id设置到中间表对象中,然后再把中间表对象添加到list中,
	遍历list,再调用保存方法把它保存到中间表的数
	据添加到数据库。
	@Override
	@Transactional
	public void add(Role role) {
		 //先添加t_role
		roleMapper.insert(role);
		//在添加t_role_permission
		List<RolePermission> list = new ArrayList<>();
		
			for (Permission ps : role.getPermissions()) {
				RolePermission rp = new RolePermission();
				rp.setRoleId(role.getId());
				rp.setPermissionId(ps.getId());
				//把rp添加到list中
				list.add(rp);
			}
		
				//添加中间表
				for (RolePermission rolePermission : list) {
					rolePermissionMapper.insert(rolePermission);
				}
			}

4.4.1.2 修改思路:

		前台:
			点击修改,需要把数据回显在模态框中的表单和数据表格中,
				//回显表单 from('load' , row)
				roleForm.form("load" , row);
				//回显数据表格
				selectPermission.datagrid("loadData" , {
					total:row.permissions.length,
					rows:row.permissions,
				})
			再点击保存,调用修改方法
		后台:
			先修改角色表
			再清空中间表的信息
				注意:在这里,清空中间表的时候,是根据角色表的id
				写sql语句到数据库进行删除的
			再添加中间表

4.4.1.3 删除思路:

		前台:
			先选中一行,点击删除,调用后台删除方法
		后台:
			先删除中间表
				注意:在这里,清空中间表的时候,是根据角色表
				id写sql语句到数据库进行删除的
			在删除角色表

=====================================================

五、授权操作:

	授权:
        回顾:
	我们使用shiro做了身份认证,还没有进行授权操作

5.1 如何使用shiro授权?

	权限拦截:
	告诉shiro哪些资源需要什么样的权限才能访问。
  		 /Dept/delete  ===  dept:delete
	两种方式:
		Xml中写死:	不好,因为数据库中有很多路径权限,我们
			需要查询出来
		 动态加载:
	写一个MapfactoryBean类 
	提供一个public  Map<String  , Object> getMap(){ //返回一个map
	Map<String , String > map = new LinkedHashMap<>();
	在这里从数据库中查询出来的权限放在map中
	配置权限拦截过滤器 查询所有的权限,遍历出来,把路径url 
	和sn放在map 中, 注意: 需要拼接,这样的话访问任何都没
	有权限,因为还没有告诉shiro
	那么在之前写的身份认证类现在需要继承AuthorizingRealm
	重写两个方法:  
		身份认证方法:加密加盐加次数 ,昨天已经操作了。
		shiro方法:
		告诉shiro当前登录人的用户都有哪些权限
		那么service应该提供个方法:通过当前登录人返回所有的
		权限,需要
		五张表  :  根据当前登录人的id查询出当前登录的所有权
		限 : 映射
		在这里查询出来之后,把所有权限字符串封装到info对象
		中。 
		       Info.setStringPermissions() //不推荐使用
		      遍历:把info.addStringPermission(遍历出的.getSn()) 
		      //返回info
		}					

5.2 授权:

 告诉shiro当前登录的用户都哪些权限?
	Dept:save     dept:page       dept:delete
	实现方式: 在reaml中的info对象,
	可以控制:
		URL地址 控制器的方法 也相当于URL地址
		有权限显示该菜单,没有权限不显示该菜单
		页面的按钮,没有权限不显示该按钮
		数据模型(重要模型)
			A只能看到A用户的顾客 B只能看到B用户的客	
			管理员可以看到A用户的顾客也可以看到B顾客
		shiro是Java中一种安全框架 ,怎么实现的?
			作用:身份认证、授权、会话管理、密码学
			Spring security :一种重量级的框架,但是功能要强大一些
			Apache shiro: 他是一种轻量级框架,用起来比较简单 相比security学习成本低一些

5.3 权限和菜单/资源:

	怎么关联起来:一对一关系
	在权限表中添加一个menu_id外键 对应菜单中的id	,
	在这里,有Url的菜单是二级菜单,那么
	也应该把一级菜单显示出来。
	
        接下来:查询当前用户的二级菜单 ,注意: 只找菜单,不需要left了,
        因为只有url的才有id菜单,null的我们不需要,在这里
        我们需要把当前登陆用户的一级菜单和二级菜单,再连一次菜单 ,
        注意: 需要排序:把相同的一级菜单放在一起,根据父菜单的id为了方便映射	

5.4 页面按钮的控制:

如果当前用户没有员工删除的权限,那么应该禁用该按钮,或者提示没有删除按钮的权限。
302是重定向
发送的异步ajax请求不可以显示一个Jsp页面,如果发送的是ajax请求,
那么就返回一个 json数据给我,如果我们发送的异步请求,而用户没有这个url的权限,我们的shiro
会拦截这个拦截,并且跳转到未授权的jsp页面,但是ajax请求是无法解析一个jsp页面的,让shiro
直接响应一个json格式的字符串,这个ajax请求解析这个json,弹出“对不起,你没有该权限。
如何实现?
	要shiro区分请求是否是ajax请求
		Index同步和 delete异步请求 中比较
		写一个类继承PermissionsAuthorizationFilter,重写onAccessDenied方法
		看请求头中是否有 X-Requested-With : XMLHttpRequested ,有就是ajax请求
		httpServletRequest req = (HttpServletReqest)request
		req.getHeader(“X-Requested-With)
	  判断X-Requested-With 并且有XMLHttpRequested.equals(header)
		String json = “{\”success\”:false,\”message\”:\”对不起,你没有这个权限\”}”
		把这个json写到响应里面去  respone.getWriter().write(json);
		中文会乱码:来一个响应头 respone.setContentType(“text/json;charset=utf-8)
		接下来需要配置,如果我们想要把自己写的过滤器,那么就需要注入到		
		ShrioFilterFactoyrBean中去, Filters: 为shiro配置自定义的过滤器
		接下来需要使用它,以前使用的而是perms,现在需要用我们自己的,功能比较强大,
		可以区分ajax请求
		授权的过滤器是perms ,shiro里面已经写好了很多的过滤器				
		在哪里区分并且做出对应的响应
		如果该用户没有这个权限,那么我可以把这个按钮不显示:
		可以使用JSTL标签  
			指令 : taglib 引入
			<shiro: hasPermission name = “dept:delete” / >就可以了

5.5 模型数据权限控制:

		两个市场人员 A、B
		A只能查询自己的客户
		B只能查询自己的客户
		但是对于超级管理员和市场部经理,可以查询所有的客户
		通过sql 语句的生成:
		在CustomerQuery对象中加两个字段
		当前用户登陆的id 
		Boolean  adminOrManaer;
		注意: 获取的时候
		Id需要返回UserContext.getUser.getId();
		adminOrManaer需要返回的是: 
			判断超级管理员和市场部经理是否等于getSn 是返回true 
			不是的话返回false;

5.6 JSON数据输出优化:

		打开注解里面配置
		Json属性设置 : 
			两个作用:
			处理responseBody里面的日期类型 只是后台到前台有作用
		               处理前台到后台日期格式话的输入  必须保留
			为null 字段不需要显示 他不会转json
	<mvc:annotation-driven>
	<mvc:message-converters>
		<!-- json属性设置 -->
		<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
			<property name="objectMapper">
				<bean class="com.fasterxml.jackson.databind.ObjectMapper">
					<property name="dateFormat">
						<!-- 处理responseBody 里面日期类型 -->
						<bean class="java.text.SimpleDateFormat">
							<constructor-arg type="java.lang.String" value="yyyy-MM-dd" />
						</bean>
					</property>
					<property name="serializationInclusion">
						<!-- 为null字段时不显示 -->
						<value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
					</property>
				</bean>
			</property>
		</bean>
	</mvc:message-converters>
</mvc:annotation-driven>

猜你喜欢

转载自blog.csdn.net/weixin_42075468/article/details/83754968
今日推荐