记录--按钮级别权限怎么控制

Posted 林恒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录--按钮级别权限怎么控制相关的知识,希望对你有一定的参考价值。

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

最近的面试中有一个面试官问我按钮级别的权限怎么控制,我说直接v-if啊,他说不够好,我说我们项目中按钮级别的权限控制情况不多,所以v-if就够了,他说不够通用,最后他对我的评价是做过很多东西,但是都不够深入,好吧,那今天我们就来深入深入。

因为我自己没有相关实践,所以接下来就从这个有16.2k星星的后台管理系统项目Vue vben admin中看看它是如何做的。

获取权限码

要做权限控制,肯定需要一个code,无论是权限码还是角色码都可以,一般后端会一次性返回,然后全局存储起来就可以了,Vue vben admin是在登录成功以后获取并保存到全局的store中:

import  defineStore  from \'pinia\';
export const usePermissionStore = defineStore(
    state: () => (
        // 权限代码列表
        permCodeList: [],
    ),
    getters: 
        // 获取
        getPermCodeList()
      		return this.permCodeList;
    	,	
    ,
    actions: 
        // 存储
        setPermCodeList(codeList) 
            this.permCodeList = codeList;
        ,

        // 请求权限码
        async changePermissionCode() 
            const codeList = await getPermCode();
            this.setPermCodeList(codeList);
        
    
)

接下来它提供了三种按钮级别的权限控制方式,一一来看。

函数方式

使用示例如下:

<template>
  <a-button v-if="hasPermission([\'20000\', \'2000010\'])" color="error" class="mx-4">
    拥有[20000,2000010]code可见
  </a-button>
</template>

<script lang="ts">
  import  usePermission  from \'/@/hooks/web/usePermission\';

  export default defineComponent(
    setup() 
      const  hasPermission  = usePermission();
      return  hasPermission ;
    ,
  );
</script>

本质上就是通过v-if,只不过是通过一个统一的权限判断方法hasPermission

export function usePermission() 
    function hasPermission(value, def = true) 
        // 默认视为有权限
        if (!value) 
            return def;
        

        const allCodeList = permissionStore.getPermCodeList;
        if (!isArray(value)) 
            return allCodeList.includes(value);
        
        // intersection是lodash提供的一个方法,用于返回一个所有给定数组都存在的元素组成的数组
        return (intersection(value, allCodeList)).length > 0;

        return true;
    

很简单,从全局store中获取当前用户的权限码列表,然后判断其中是否存在当前按钮需要的权限码,如果有多个权限码,只要满足其中一个就可以。

组件方式

除了通过函数方式使用,也可以使用组件方式,Vue vben admin提供了一个Authority组件,使用示例如下:

<template>
  <div>
    <Authority :value="RoleEnum.ADMIN">
      <a-button type="primary" block> 只有admin角色可见 </a-button>
    </Authority>
  </div>
</template>
<script>
  import  Authority  from \'/@/components/Authority\';
  import  defineComponent  from \'vue\';
  export default defineComponent(
    components:  Authority ,
  );
</script>

使用Authority包裹需要权限控制的按钮即可,该按钮需要的权限码通过value属性传入,接下来看看Authority组件的实现。

<script lang="ts">
  import  defineComponent  from \'vue\';
  import  usePermission  from \'/@/hooks/web/usePermission\';
  import  getSlot  from \'/@/utils/helper/tsxHelper\';

  export default defineComponent(
    name: \'Authority\',
    props: 
      value: 
        type: [Number, Array, String],
        default: \'\',
      ,
    ,
    setup(props,  slots ) 
      const  hasPermission  = usePermission();

      function renderAuth() 
        const  value  = props;
        if (!value) 
          return getSlot(slots);
        
        return hasPermission(value) ? getSlot(slots) : null;
      

      return () => 
        return renderAuth();
      ;
    ,
  );
</script>

同样还是使用hasPermission方法,如果当前用户存在按钮需要的权限码时就原封不动渲染Authority包裹的内容,否则就啥也不渲染。

指令方式

最后一种就是指令方式,使用示例如下:

<a-button v-auth="\'1000\'" type="primary" class="mx-4"> 拥有code [\'1000\']权限可见 </a-button>

实现如下:

import  usePermission  from \'/@/hooks/web/usePermission\';

function isAuth(el, binding) 
  const  hasPermission  = usePermission();

  const value = binding.value;
  if (!value) return;
  if (!hasPermission(value)) 
    el.parentNode?.removeChild(el);
  


const mounted = (el, binding) => 
  isAuth(el, binding);
;

const authDirective = 
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted,
;

// 注册全局指令
export function setupPermissionDirective(app) 
  app.directive(\'auth\', authDirective);

只定义了一个mounted钩子,也就是在绑定元素挂载后调用,依旧是使用hasPermission方法,判断当前用户是否存在通过指令插入的按钮需要的权限码,如果不存在,直接移除绑定的元素。

很明显,Vue vben admin的实现有两个问题,一是不能动态更改按钮的权限,二是动态更改当前用户的权限也不会生效。

解决第一个问题很简单,因为上述只有删除元素的逻辑,没有加回来的逻辑,那么增加一个updated钩子:

app.directive("auth", 
    mounted: (el, binding) => 
        const value = binding.value
        if (!value) return
        if (!hasPermission(value)) 
            // 挂载的时候没有权限把元素删除
            removeEl(el)
        
    ,
    updated(el, binding) 
        // 按钮权限码没有变化,不做处理
        if (binding.value === binding.oldValue) return
        // 判断用户本次和上次权限状态是否一样,一样也不用做处理
        let oldHasPermission = hasPermission(binding.oldValue)
        let newHasPermission = hasPermission(binding.value)
        if (oldHasPermission === newHasPermission) return
        // 如果变成有权限,那么把元素添加回来
        if (newHasPermission) 
            addEl(el)
         else 
        // 如果变成没有权限,则把元素删除
            removeEl(el)
        
    ,
)

const hasPermission = (value) => 
    return [1, 2, 3].includes(value)


const removeEl = (el) => 
    // 在绑定元素上存储父级元素
    el._parentNode = el.parentNode
    // 在绑定元素上存储一个注释节点
    el._placeholderNode = document.createComment("auth")
    // 使用注释节点来占位
    el.parentNode?.replaceChild(el._placeholderNode, el)


const addEl = (el) => 
    // 替换掉给自己占位的注释节点
    el._parentNode?.replaceChild(el, el._placeholderNode)

主要就是要把父节点保存起来,不然想再添加回去的时候获取不到原来的父节点,另外删除的时候创建一个注释节点给自己占位,这样下次想要回去能知道自己原来在哪。

第二个问题的原因是修改了用户权限数据,但是不会触发按钮的重新渲染,那么我们就需要想办法能让它触发,这个可以使用watchEffect方法,我们可以在updated钩子里通过这个方法将用户权限数据和按钮的更新方法关联起来,这样当用户权限数据改变了,可以自动触发按钮的重新渲染:

import  createApp, reactive, watchEffect  from "vue"
const codeList = reactive([1, 2, 3])

const hasPermission = (value) => 
    return codeList.includes(value)


app.directive("auth", 
    updated(el, binding) 
        let update = () => 
            let valueNotChange = binding.value === binding.oldValue
            let oldHasPermission = hasPermission(binding.oldValue)
            let newHasPermission = hasPermission(binding.value)
            let permissionNotChange = oldHasPermission === newHasPermission
            if (valueNotChange && permissionNotChange) return
            if (newHasPermission) 
                addEl(el)
             else 
                removeEl(el)
            
        ;
        if (el._watchEffect) 
            update()
         else 
            el._watchEffect = watchEffect(() => 
                update()
            )
        
    ,
)

updated钩子里更新的逻辑提取成一个update方法,然后第一次更新在watchEffect中执行,这样用户权限的响应式数据就可以和update方法关联起来,后续用户权限数据改变了,可以自动触发update方法的重新运行。

本文转载于:

https://juejin.cn/post/7209648356530896953

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

(转)通过SpringMVC+Annotation实现方法按钮级别的细粒度权限控制

随着企业信息化的不断深入,各种各样的信息系统成为提高企业运营及管理效率的必备工具,越来越多的企业核心机密如销售机会、客户资料、设计方案等通过信息系统存储、备案、流转,这些核心资料一旦外泄,势必对企业造成极大损失。科技时代,信息是企业生存的命脉,信息的安全也必然成为企业极度重视的问题。如今,随着各种信息安全措施的实施,信息泄密已经从外部泄漏向内部人员泄漏转移。外部的黑客、病毒要想获取有价值的信息,必须穿透多道防火墙,逃避多重杀毒工具的追杀,再对信息进行筛选才能如愿以偿;而内部人员知道什么信息是有价值的,如果不对信息进行必要的安全防护,企业内部一些有有心人员会十分容易地获取自己所需要的信息资料。

最近的一份调查显示,几乎有一半的各行各业专业人士承认当他们跳槽时会带走资料,包括文件、销售协议和合同清单等各种资料,并将它们告诉下一个老板。调查还发现,八成的职员可以轻松地下载“有竞争力”的资料和信息,然后带到下一份工作中。

信息安全任重而道远。要保证信息系统的安全,需要考虑到很多方面如防火墙、加密传输、防SQL注入等,但很多的安全方案都是从如何把守大门着手的,如身份认证、数字证书,不管是传统的用户名加口令方式还是基于生物特征识别的指纹、视网膜扫描技术,乃至各类电子政务领域常用的USBkey都是在进入系统大门时大做文章,一旦身份识别完成进入大门后,却听之任之,很少再有处理方案。本文重点不在如何进行身份认证,而在身份认证完成后也即进入系统大门后,如何保证用户只在自己有权限的范围内进行操作,而不是可以进行任意功能的操作即系统内部细粒度权限控制解决方案。


常用的权限系统设计模式是以角色为核心的,即角色是具有相同权限的一类人员的集合:

1.     一个角色可以有包含多个操作人员,一个操作人员也可以属于多个角色

2.     一个角色可以具有多个功能的操作权限,一个功能也可以被多个角色所拥有。

在登录时通过查询登录用户所属角色,即可得到个用户的所有功能集合,如下图:



多数业务系统的页面功能菜单设计是以三级为标准的,即一级功能菜单、二级功能菜单、三级功能菜单,通常情况下一二级功能菜单只是用于功能分类,是不具有功能访问地址的,三级菜单才是功能的真正入口,常规权限系统就是通过控制每个人员对应的功能菜单的显示与隐藏来实现权限控制。要实现细粒度权限控制,可在设计功能表时再加入第四层:页面元素,隶属于第三层功能菜单,这些页面元素用来标识功能页面中的每一个功能按钮,如增加、修改、删除、查询都可算是页面元素,在为角色分配权限时,第四层也同样纳入统一权限管理,如果有此页面元素的权限,则页面上就显示该按钮,如果没有此页面元素的功能权限,则该按钮就不会显示出来。


对于没有权限访问的功能或页面除了进行前台的隐藏之外,还需要在后台访问时进行权限的验证,否则操作人员绕开页面直接通过输入URL访问功能就会造成权限漏洞,通过SpringMVC+Annotation的方式可以轻松实现,代码如下:

第一步:创建SpringMVC拦截器,拦截所有需要进行权限验证的功能请求

<!-- 开启注解 -->
	<mvc:annotation-driven/>
	
	
	<!-- 静态资源访问 -->
	 <mvc:resources location="/static/" mapping="/static/**"/> 
	  
	 <!-- 拦截器 -->
	  <mvc:interceptors>  
        <!-- 多个拦截器,顺序执行 -->  
        <mvc:interceptor>  
          <!-- 如果不配置或/**,将拦截所有的Controller -->
           <mvc:mapping path="/**" /> 
           <!-- 在Freemarker界面展示之前做一些通用处理   -->
           <bean class="xx.xxxx.core.web.FreeMarkerViewInterceptor"></bean>  
        </mvc:interceptor>  
    </mvc:interceptors>  


第二步:创建作用于Method级别的Annotation类,用于传入功能ID

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Permission {

	/**
	 * 功能ID,该功能ID,对应数据库中的功能ID
	 * @return
	 * @version V1.0.0
	 * @date Jan 13, 2014 4:59:35 PM
	 */
	String value();
	
}
第三步:通过静态常量建立数据库中的功能ID与执行方法的一对一关系

public class FuncConstants {

	/**
	 * 系统管理-角色管理-增加角色
	 */
	public final static String Xtgl_Jsgl_AddJs = "4399d98bb0d84114acb5693081e83bc9";
	/**
	 * 系统管理 - 部门管理- 部门列表
	 */
	public final static String Xtgl_Bmgl_BmList = "dbc4bf80f8b6418788b79de204d37932";
	
}

第四步:在SpringMVC拦截器中验证权限

/**
 * FreeMarker视图拦截器,页面展示之前做一些通用处理
 * @version V1.0.0
 * @date Dec 12, 2013 4:20:04 PM
 */
public class FreeMarkerViewInterceptor extends HandlerInterceptorAdapter {

	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception {
		
	}

	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object arg2, ModelAndView view) throws Exception {
		String contextPath = request.getContextPath();
		if (view != null) {
			request.setAttribute("base", contextPath);
		}
	}
  
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		
		//处理Permission Annotation,实现方法级权限控制
		HandlerMethod method = (HandlerMethod)handler;
		Permission permission = method.getMethodAnnotation(Permission.class);
		
		//如果为空在表示该方法不需要进行权限验证
		if (permission == null) {
			return true;
		}
		
		//验证是否具有权限
		if (!WebUtil.hasPower(request, permission.value())) {
			response.sendRedirect(request.getContextPath()+"/business/nopermission.html");
			return false;
		}
		return true;
		
		
		//注意此处必须返回true,否则请求将停止
		//return true;
	}

}

至此,基于按钮、方法验证的细粒度权限体系完成!



以上是关于记录--按钮级别权限怎么控制的主要内容,如果未能解决你的问题,请参考以下文章

面试官:Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?

vue项目中的按钮权限配置

Vue多页面 按钮级别权限控制 directive指令控制

vueconfirm如何控制按钮

(转)通过SpringMVC+Annotation实现方法按钮级别的细粒度权限控制

通过SpringMVC+Annotation实现方法按钮级别的细粒度权限控制