RuoYi-Vue————权限管理

RuoYi-Vue————权限管理

    • 1. 若依框架权限分类
    • 2. 若依框架权限的依次介绍
    • 3. 若依框架重要接口执行流程

1. 若依框架权限分类

若依Vue系统中的权限分为以下几类:

1 菜单权限:用户登录系统之后能看到哪些菜单
2 按钮权限:用户在一个页面上能看到哪些按钮,比如新增、删除等按钮
3 接口权限:用户带着认证信息请求后端接口,是否有权限访问,该接口和前端页面上的按钮一一对应
4 数据权限:用户有权限访问后端某个接口,但是不同的用户相同的接口相同的入参,根据权限大小不同,返回的结果应当不一样——权限大的能够看到的数据更多。

2. 若依框架权限的依次介绍

1 菜单权限
这个比较好理解,拥有不同权限的用户登录系统之后看到的菜单是不一样的。(新建菜单、用户分配菜单权限即可)

具体代码1(后端):SysLoginController-------->getRouters()

select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
		from sys_menu m
			 left join sys_role_menu rm on m.menu_id = rm.menu_id
			 left join sys_user_role ur on rm.role_id = ur.role_id
			 left join sys_role ro on ur.role_id = ro.role_id
			 left join sys_user u on ur.user_id = u.user_id
		where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0  AND ro.status = 0
		order by m.parent_id, m.order_num

这是典型的用户-角色-菜单模型。 菜单类型(M目录 C菜单 F按钮);
菜单状态(0显示 1隐藏)
前端会根据该接口返回的数据渲染出不同的菜单。

数据的结构如下图所示:(前端动态路由)
RuoYi-Vue————权限管理_第1张图片
具体代码2(前端):ruoyi-ui\src\permission.js

permission.js文件中设置了导航守卫,每次路由发生变化的时候就会触发router.beforeEach的回调函数。

// 路由白名单
const whiteList = ['/login', '/auth-redirect', '/bind', '/register']

// 路由守卫
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.roles.length === 0) {
        // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
          store.dispatch('GenerateRoutes').then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            router.addRoutes(accessRoutes) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch(err => {
            store.dispatch('LogOut').then(() => {
              Message.error(err)
              next({ path: '/' })
            })
          })
      } else {
        next()
      }
    }
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})
router.afterEach(() => {
  NProgress.done()
})

注意if (store.getters.roles.length === 0) {这段逻辑,可以看出,如果不刷新当前页面,就算给用户添加了新的菜单权限,用户也看不到新的菜单

2.按钮权限
新增、删除、查看、修改等按钮,当前用户是否能用
具体代码1(后端):SysLoginController-------->getInfo() (前端拿到会存到vuex中)

    // 将来这些数据,前端拿到会存到vuex中
    @GetMapping("getInfo")
    public AjaxResult getInfo()
    {
        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
        SysUser user = loginUser.getUser();
        // 角色集合
        Set<String> roles = permissionService.getRolePermission(user);
        // 权限集合
        Set<String> permissions = permissionService.getMenuPermission(user);
        AjaxResult ajax = AjaxResult.success();
        // 根据当前用户,获取当前用户的所有user、roles、permissions信息,并返回
        ajax.put("user", user);
        ajax.put("roles", roles);
        ajax.put("permissions", permissions);
        return ajax;
    }

具体代码2(前端):ruoyi-ui\src\directive\permission
具体用法:

	<el-button size="mini" 
            type="text" 
            icon="el-icon-edit" 
            @click="handleUpdate(scope.row)"
            v-hasPermi="['system:menu:edit']"   这里就是按钮权限      
          >修改
    el-button>
    
    <el-button 
            size="mini" 
            type="text" 
            icon="el-icon-plus" 
            @click="handleAdd(scope.row)"
            v-hasPermi="['system:menu:add']"
          >新增
    el-button>

按钮权限 v-hasPermi 的具体实现:

export default {
  inserted(el, binding, vnode) {
    const { value } = binding
    const all_permission = "*:*:*";
    // 从vuex中获取数据
    const permissions = store.getters && store.getters.permissions

    if (value && value instanceof Array && value.length > 0) {
      const permissionFlag = value

      const hasPermissions = permissions.some(permission => {
        return all_permission === permission || permissionFlag.includes(permission)
      })
      // 如果没有 则移除removeChild
      if (!hasPermissions) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置操作权限标签值`)
    }
  }
}

3 接口权限:
接口权限和前端的按钮权限一一对应。为的是防止用户绕过按钮直接请求后端接口获取数据。在若依Vue系统中,是使用SpringSecurity的注解@PreAuthorize实现的。

具体代码实现(这里只有后端)

    @PreAuthorize("@ss.hasPermi('system:menu:edit')")
    @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@Validated @RequestBody SysMenu menu)
    {
        if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu)))
        {
            return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
        }
        else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
        {
            return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
        }
        else if (menu.getMenuId().equals(menu.getParentId()))
        {
            return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
        }
        menu.setUpdateBy(getUsername());
        return toAjax(menuService.updateMenu(menu));
    }

通过 @PreAuthorize("@ss.hasPermi(‘system:menu:edit’)")注解,实现了接口权限

接下来我们看 @PreAuthorize("@ss.hasPermi(‘system:menu:edit’)")

    /**
     * 验证用户是否具备某权限
     * 
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPermi(String permission)
    {
        if (StringUtils.isEmpty(permission))
        {
            return false;
        }
        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
        {
            return false;
        }
        return hasPermissions(loginUser.getPermissions(), permission);
    }

4 数据权限:
数据权限实现的关键在于com.ruoyi.framework.aspectj.DataScopeAspect类。该类是一个切面类,凡是加上com.ruoyi.common.annotation.DataScope注解的方法,在执行的时候都会被它拦截。

该切面定义了五种权限范围

该切面的核心逻辑是“拼SQL”,方法执行之前,会给参数的一个params属性添加一个dataScope键值对,key为"dataScope",值为AND (" + sqlString.substring(4) + ")"样式的一段SQL,这段SQL会根据当前用户所在的部门以及当前用户角色的权限范围发生变化。

简单来说,这段代码的逻辑就是用户所在的部门权限越高,数据权限范围越大,查出来的结果集将会越大。

DataScope注解分别加到了部门列表查询、角色列表查询、用户列表查询的接口上,很明显,这几个接口需要根据不同的人查出不同的结果。
以用户列表查询为例,执行sql为

    <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
		select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
		left join sys_dept d on u.dept_id = d.dept_id
		where u.del_flag = '0'
		<if test="userName != null and userName != ''">
			AND u.user_name like concat('%', #{userName}, '%')
		</if>
		<if test="status != null and status != ''">
			AND u.status = #{status}
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
			AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
		</if>
		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
			AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
		</if>
		<if test="deptId != null and deptId != 0">
			AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>

实际上DataScopeAspect切面就只干了填充params的dataScope属性这么一件事情。(把之前拼好的sql扔这里)

3. 若依框架重要接口执行流程

login
RuoYi-Vue————权限管理_第2张图片
getInfo
RuoYi-Vue————权限管理_第3张图片
getRouter
RuoYi-Vue————权限管理_第4张图片
比较好的文章推荐:

https://blog.csdn.net/qq_42778001/article/details/119956713

你可能感兴趣的:(若依框架源码,java,spring,后端,spring,boot,spring,5)