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
1
2
3
4
5
6
7
8
9
這是典型的用戶-角色-菜單模型。 菜單類型(M目錄 C菜單 F按鈕);
菜單狀態(0顯示 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()
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
注意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;
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
具體代碼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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
按鈕權限 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(`請設置操作權限標簽值`)
? ? }
? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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));
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
通過 @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);
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
實際上DataScopeAspect切面就只干了填充params的dataScope屬性這么一件事情。(把之前拼好的sql扔這里)
3. 若依框架重要接口執行流程
login
getInfo
getRouter
是不是發現上述兩步不太嚴謹,對,我們可以自定義規則搞事情
?
總結
以上是生活随笔為你收集整理的RuoYi-Vue————权限管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 若依文件分析
- 下一篇: RuoYi后台系统权限管理解析