RuoYi-Vue————权限管理
RuoYi-Vue————權(quán)限管理
1. 若依框架權(quán)限分類(lèi)
2. 若依框架權(quán)限的依次介紹
3. 若依框架重要接口執(zhí)行流程
1. 若依框架權(quán)限分類(lèi)
若依Vue系統(tǒng)中的權(quán)限分為以下幾類(lèi):
1 菜單權(quán)限:用戶(hù)登錄系統(tǒng)之后能看到哪些菜單
2 按鈕權(quán)限:用戶(hù)在一個(gè)頁(yè)面上能看到哪些按鈕,比如新增、刪除等按鈕
3 接口權(quán)限:用戶(hù)帶著認(rèn)證信息請(qǐng)求后端接口,是否有權(quán)限訪問(wèn),該接口和前端頁(yè)面上的按鈕一一對(duì)應(yīng)
4 數(shù)據(jù)權(quán)限:用戶(hù)有權(quán)限訪問(wèn)后端某個(gè)接口,但是不同的用戶(hù)相同的接口相同的入?yún)?#xff0c;根據(jù)權(quán)限大小不同,返回的結(jié)果應(yīng)當(dāng)不一樣——權(quán)限大的能夠看到的數(shù)據(jù)更多。
2. 若依框架權(quán)限的依次介紹
1 菜單權(quán)限
這個(gè)比較好理解,擁有不同權(quán)限的用戶(hù)登錄系統(tǒng)之后看到的菜單是不一樣的。(新建菜單、用戶(hù)分配菜單權(quán)限即可)
具體代碼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
這是典型的用戶(hù)-角色-菜單模型。 菜單類(lèi)型(M目錄 C菜單 F按鈕);
菜單狀態(tài)(0顯示 1隱藏)
前端會(huì)根據(jù)該接口返回的數(shù)據(jù)渲染出不同的菜單。
數(shù)據(jù)的結(jié)構(gòu)如下圖所示:(前端動(dòng)態(tài)路由)
具體代碼2(前端):ruoyi-ui\src\permission.js
permission.js文件中設(shè)置了導(dǎo)航守衛(wèi),每次路由發(fā)生變化的時(shí)候就會(huì)觸發(fā)router.beforeEach的回調(diào)函數(shù)。
// 路由白名單
const whiteList = ['/login', '/auth-redirect', '/bind', '/register']
// 路由守衛(wèi)
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) {
? ? ? ? // 判斷當(dāng)前用戶(hù)是否已拉取完user_info信息
? ? ? ? store.dispatch('GetInfo').then(() => {
? ? ? ? ? store.dispatch('GenerateRoutes').then(accessRoutes => {
? ? ? ? ? ? // 根據(jù)roles權(quán)限生成可訪問(wèn)的路由表
? ? ? ? ? ? router.addRoutes(accessRoutes) // 動(dòng)態(tài)添加可訪問(wèn)路由表
? ? ? ? ? ? next({ ...to, replace: true }) // hack方法 確保addRoutes已完成
? ? ? ? ? })
? ? ? ? }).catch(err => {
? ? ? ? ? ? store.dispatch('LogOut').then(() => {
? ? ? ? ? ? ? Message.error(err)
? ? ? ? ? ? ? next({ path: '/' })
? ? ? ? ? ? })
? ? ? ? ? })
? ? ? } else {
? ? ? ? next()
? ? ? }
? ? }
? } else {
? ? // 沒(méi)有token
? ? if (whiteList.indexOf(to.path) !== -1) {
? ? ? // 在免登錄白名單,直接進(jìn)入
? ? ? next()
? ? } else {
? ? ? next(`/login?redirect=${to.fullPath}`) // 否則全部重定向到登錄頁(yè)
? ? ? 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) {這段邏輯,可以看出,如果不刷新當(dāng)前頁(yè)面,就算給用戶(hù)添加了新的菜單權(quán)限,用戶(hù)也看不到新的菜單。
2.按鈕權(quán)限
新增、刪除、查看、修改等按鈕,當(dāng)前用戶(hù)是否能用
具體代碼1(后端):SysLoginController-------->getInfo() (前端拿到會(huì)存到vuex中)
? ? // 將來(lái)這些數(shù)據(jù),前端拿到會(huì)存到vuex中
? ? @GetMapping("getInfo")
? ? public AjaxResult getInfo()
? ? {
? ? ? ? LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
? ? ? ? SysUser user = loginUser.getUser();
? ? ? ? // 角色集合
? ? ? ? Set<String> roles = permissionService.getRolePermission(user);
? ? ? ? // 權(quán)限集合
? ? ? ? Set<String> permissions = permissionService.getMenuPermission(user);
? ? ? ? AjaxResult ajax = AjaxResult.success();
? ? ? ? // 根據(jù)當(dāng)前用戶(hù),獲取當(dāng)前用戶(hù)的所有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']" ? 這里就是按鈕權(quán)限 ? ? ?
? ? ? ? ? >修改
? ? </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
按鈕權(quán)限 v-hasPermi 的具體實(shí)現(xiàn):
export default {
? inserted(el, binding, vnode) {
? ? const { value } = binding
? ? const all_permission = "*:*:*";
? ? // 從vuex中獲取數(shù)據(jù)
? ? 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)
? ? ? })
? ? ? // 如果沒(méi)有 則移除removeChild
? ? ? if (!hasPermissions) {
? ? ? ? el.parentNode && el.parentNode.removeChild(el)
? ? ? }
? ? } else {
? ? ? throw new Error(`請(qǐng)?jiān)O(shè)置操作權(quán)限標(biāo)簽值`)
? ? }
? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
3 接口權(quán)限:
接口權(quán)限和前端的按鈕權(quán)限一一對(duì)應(yīng)。為的是防止用戶(hù)繞過(guò)按鈕直接請(qǐng)求后端接口獲取數(shù)據(jù)。在若依Vue系統(tǒng)中,是使用SpringSecurity的注解@PreAuthorize實(shí)現(xiàn)的。
具體代碼實(shí)現(xiàn)(這里只有后端)
? ? @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() + "'失敗,菜單名稱(chēng)已存在");
? ? ? ? }
? ? ? ? else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath()))
? ? ? ? {
? ? ? ? ? ? return AjaxResult.error("修改菜單'" + menu.getMenuName() + "'失敗,地址必須以http(s)://開(kāi)頭");
? ? ? ? }
? ? ? ? else if (menu.getMenuId().equals(menu.getParentId()))
? ? ? ? {
? ? ? ? ? ? return AjaxResult.error("修改菜單'" + menu.getMenuName() + "'失敗,上級(jí)菜單不能選擇自己");
? ? ? ? }
? ? ? ? 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
通過(guò) @PreAuthorize("@ss.hasPermi(‘system:menu:edit’)")注解,實(shí)現(xiàn)了接口權(quán)限
接下來(lái)我們看 @PreAuthorize("@ss.hasPermi(‘system:menu:edit’)")
? ? /**
? ? ?* 驗(yàn)證用戶(hù)是否具備某權(quán)限
? ? ?*?
? ? ?* @param permission 權(quán)限字符串
? ? ?* @return 用戶(hù)是否具備某權(quán)限
? ? ?*/
? ? 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 數(shù)據(jù)權(quán)限:
數(shù)據(jù)權(quán)限實(shí)現(xiàn)的關(guān)鍵在于com.ruoyi.framework.aspectj.DataScopeAspect類(lèi)。該類(lèi)是一個(gè)切面類(lèi),凡是加上com.ruoyi.common.annotation.DataScope注解的方法,在執(zhí)行的時(shí)候都會(huì)被它攔截。
該切面定義了五種權(quán)限范圍
該切面的核心邏輯是“拼SQL”,方法執(zhí)行之前,會(huì)給參數(shù)的一個(gè)params屬性添加一個(gè)dataScope鍵值對(duì),key為"dataScope",值為AND (" + sqlString.substring(4) + ")"樣式的一段SQL,這段SQL會(huì)根據(jù)當(dāng)前用戶(hù)所在的部門(mén)以及當(dāng)前用戶(hù)角色的權(quán)限范圍發(fā)生變化。
簡(jiǎn)單來(lái)說(shuō),這段代碼的邏輯就是用戶(hù)所在的部門(mén)權(quán)限越高,數(shù)據(jù)權(quán)限范圍越大,查出來(lái)的結(jié)果集將會(huì)越大。
DataScope注解分別加到了部門(mén)列表查詢(xún)、角色列表查詢(xún)、用戶(hù)列表查詢(xún)的接口上,很明顯,這幾個(gè)接口需要根據(jù)不同的人查出不同的結(jié)果。
以用戶(hù)列表查詢(xún)?yōu)槔?#xff0c;執(zhí)行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 != ''"><!-- 開(kāi)始時(shí)間檢索 -->
?? ??? ??? ?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 != ''"><!-- 結(jié)束時(shí)間檢索 -->
?? ??? ??? ?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>
?? ??? ?<!-- 數(shù)據(jù)范圍過(guò)濾 -->
?? ??? ?${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
實(shí)際上DataScopeAspect切面就只干了填充params的dataScope屬性這么一件事情。(把之前拼好的sql扔這里)
3. 若依框架重要接口執(zhí)行流程
login
getInfo
getRouter
是不是發(fā)現(xiàn)上述兩步不太嚴(yán)謹(jǐn),對(duì),我們可以自定義規(guī)則搞事情
?
總結(jié)
以上是生活随笔為你收集整理的RuoYi-Vue————权限管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 若依文件分析
- 下一篇: RuoYi后台系统权限管理解析