1. 準(zhǔn)備工作
在上一篇文章中,我們介紹了wall-auth的基本使用,本篇文章將介紹如何快速接入wall-auth完成權(quán)限管理。
因?yàn)槲覜]有別的Demo項(xiàng)目,所以我就直接以后端作為關(guān)鍵字在github進(jìn)行搜索,選擇了排在第一的Java項(xiàng)目linlinjava/litemall,這個項(xiàng)目是一個商城項(xiàng)目,我將在這個項(xiàng)目的基礎(chǔ)上進(jìn)行接入。
# clone項(xiàng)目
git clone https://github.com/linlinjava/litemall
cd litemall
# 將項(xiàng)目中的初始化sql文件導(dǎo)入數(shù)據(jù)庫
mysql -u root < litemall-db/litemall_schema.sql
mysql -u root -p wall < litemall-db/litemall_table.sql
mysql -u root -p wall < litemall-db/litemall_data.sql
# 編譯后端服務(wù)并啟動
mvn clean install -DskipTests -T 1C
java -Dfile.encoding=UTF-8 -jar litemall-all/target/litemall-all-0.1.0-exec.jar
# 編譯前端服務(wù)并啟動
cd litemall-admin
npm install
npm run dev
接下來我們打開瀏覽器并輸入用戶名密碼admin123/admin123登錄后臺管理系統(tǒng),我們可以看到如下界面:

這就代表我們正常啟動了litemall項(xiàng)目,接下來我們將在這個項(xiàng)目中接入wall-auth。
接入wall-auth
-
在
litemall項(xiàng)目中添加wall-auth的依賴,打開litemall-db/pom.xml文件,添加如下依賴:<dependency> <groupId>com.github.moruke</groupId> <artifactId>wall-auth</artifactId> <version>1.1.1-SNAPSHOT</version> </dependency> -
在
litemall項(xiàng)目中添加wall-auth的配置,打開litemall-db/src/main/resources/application-db.yml文件,添加如下配置:spring: mvc: pathmatch: matching-strategy: ant_path_matcher mybatis: mapper-locations: - classpath:mapperxml/account/*.xml - classpath:mapperxml/auth/*.xml - classpath:org/linlinjava/litemall/db/dao/*.xml wall: plugin: auth: component: type: casbin casbin: model-path: casbin/model.conf -
litemall-all/src/main/resources目錄下創(chuàng)建casbin目錄,并在casbin目錄下創(chuàng)建model.conf文件,內(nèi)容如下:[request_definition] r = sub, dom, obj, act [policy_definition] p = sub, dom, obj, act, eft, priority [role_definition] g = _, _, _ g2 = _, _, _ [policy_effect] e = priority(p.eft) || deny [matchers] m = (g(r.sub, p.sub, r.dom) && g2(r.obj, p.obj, r.dom) && r.dom == p.dom && r.act == p.act) || r.sub == root 初始化
wall-auth所需的表結(jié)構(gòu),將wall-auth/src/main/resources/v1.0.0/init_ddl.sql文件內(nèi)容導(dǎo)入litemall數(shù)據(jù)庫。-
修改
org.linlinjava.litemall.Application類,使用以下內(nèi)容替換同名注解:@SpringBootApplication(scanBasePackages = {"org.linlinjava.litemall.db", "org.linlinjava.litemall.core", "org.linlinjava.litemall.admin", "com.github.moruke.wall.auth"}) @MapperScan({"org.linlinjava.litemall.db.dao", "com.github.moruke.wall.auth.dao.mapper"}) -
讓我們重新啟動
litemall項(xiàng)目,只要我們能正常啟動litemall項(xiàng)目,就代表我們已經(jīng)成功接入了wall-auth。如果出現(xiàn)了類似異常:*************************** APPLICATION FAILED TO START *************************** Description: The bean 'permissionMapper' could not be injected as a 'org.linlinjava.litemall.db.dao.LitemallPermissionMapper' because it is a JDK dynamic proxy that implements: Action: Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching. Process finished with exit code 0請將
org.linlinjava.litemall.db.service.LitemallPermissionService和org.linlinjava.litemall.db.service.LitemallRoleService中,使用的@Resource替換為@Autowired。
之所以發(fā)生這個問題,是因?yàn)?code>org.linlinjava.litemall.db.dao.LitemallRoleMapper和com.github.moruke.wall.auth.dao.mapper.RoleMapper以及org.linlinjava.litemall.db.dao.LitemallPermissionMapper和com.github.moruke.wall.auth.dao.mapper.PermissionMapper在使用@Resource注解時,會出現(xiàn)同名沖突,導(dǎo)致Spring無法注入正確的Bean。
2. 修改litemall項(xiàng)目
我們選擇對通用問題的前后端進(jìn)行調(diào)整,以支持細(xì)粒度的權(quán)限控制。
不過在這開始前,我們先給予商場管理員權(quán)限,以便我們能夠正常的使用通用問題。(這部分能力litemall已經(jīng)實(shí)現(xiàn),我們只需要將其授權(quán)給商場管理員即可;只不過litemall的權(quán)限管理尚未做到細(xì)粒度,只能選擇整個模塊進(jìn)行授權(quán),而不能對指定的數(shù)據(jù)進(jìn)行授權(quán)。而我們要修改并實(shí)現(xiàn)的就是這個功能。)
使用admin123登錄后打開系統(tǒng)管理/角色管理,點(diǎn)擊商場管理員后的授權(quán)按鈕,我們可以看到如下界面:

請將商場管理下通用問題下發(fā)給商場管理員:

授權(quán)成功后我們退出登錄,使用mall123/mall123登錄后臺管理系統(tǒng),我們可以看到如下界面:

這樣mall123就可以正常的使用通用問題。
接下來我們將對通用問題相關(guān)代碼進(jìn)行改造,以支持細(xì)粒度的權(quán)限控制。我們將會給每個問題都綁定上manage權(quán)限,只有擁有manage權(quán)限的用戶才能對問題進(jìn)行操作。而其余用戶只能查看問題。而創(chuàng)建問題的用戶將會被賦予manage權(quán)限。
2.0 修改數(shù)據(jù)庫
執(zhí)行以下語句:
-- 插入`manage`權(quán)限點(diǎn):
INSERT INTO `litemall`.`action` (`id`, `name`, `description`, `type`, `status`, `domain_id`, `creator`, `mender`, `create_time`, `modify_time`) VALUES (1, 'manage', 'manage', 0, 0, 1, 1, 1, '2024-06-15 02:51:36.581', '2024-06-15 02:51:36.581');
-- 刪除`litemall_issue`表中的數(shù)據(jù):
truncate table `litemall`.`litemall_issue`;
2.1 修改后端
-
創(chuàng)建問題時,授予當(dāng)前用戶
manage權(quán)限。
打開org.linlinjava.litemall.admin.web.AdminIssueController類,找到create方法,添加如下代碼(添加在return之前):// 將問題作為Object添加到權(quán)限系統(tǒng) final ObjectDto objectDto = new ObjectDto(); objectDto.setName(issue.getQuestion()); objectDto.setCreator(getUserId()); objectDto.setDomainDto(new DomainDto(1L)); objectDto.setMender(getUserId()); objectDto.setType(ObjectTypeEnum.DEFAULT); objectDto.setStatus(ObjectStatusEnum.DEFAULT); final Long objectId = objectImpl.add(objectDto); // domainId先寫死為1 final ActionDto manage = actionImpl.get("manage", 1L); // 授予當(dāng)前用戶對問題的所有權(quán)限 final PermissionDto permissionDto = new PermissionDto(); permissionDto.setDomainDto(new DomainDto(1L)); permissionDto.setActionId(manage.getId()); permissionDto.setObjectId(objectId); permissionDto.setSubjectId(getUserId()); permissionDto.setSubjectType(SubjectTypeEnum.USER); permissionDto.setCreator(getUserId()); permissionDto.setMender(getUserId()); permissionDto.setEffect(EffectTypeEnum.ALLOW); permissionDto.setStatus(PermissionStatusEnum.DEFAULT); permissionDto.setType(PermissionTypeEnum.DEFAULT); permissionDto.setPriority(PriorityEnum.HIGHEST); rbacImpl.addPermissionForSubject(permissionDto);private Long getUserId() { // 通過SecurityUtils獲取當(dāng)前用戶 final LitemallAdmin principal = (LitemallAdmin) SecurityUtils.getSubject().getPrincipal(); return Long.valueOf(principal.getId()); }這段代碼,在創(chuàng)建問題時,會將問題作為
Object添加到權(quán)限系統(tǒng),然后授予當(dāng)前用戶對問題的manage權(quán)限。 -
查看問題列表時,補(bǔ)充當(dāng)前用戶對每個問題所擁有的權(quán)限。
打開org.linlinjava.litemall.admin.web.AdminIssueController類,找到list方法,添加如下代碼(添加在return之前):// 補(bǔ)齊當(dāng)前用戶對這些問題的權(quán)限 for (LitemallIssue issue : issueList) { final ObjectDto objectDto = objectImpl.get(issue.getQuestion(), 1L); final List<PermissionDto> permissions = rbacImpl.getPermissionsForSubject(getUserId(), SubjectTypeEnum.USER, objectDto.getId(), 1L); final List<Long> actionIds = permissions.stream().map(PermissionDto::getActionId).collect(Collectors.toList()); final List<String> actionNames = actionIds.stream().map(id -> actionImpl.get(id).getName()).collect(Collectors.toList()); issue.setActions(actionNames); }打開
org.linlinjava.litemall.db.domain.LitemallIssue類,添加如下代碼:private List<String> actions; public List<String> getActions() { return actions; } public void setActions(List<String> actions) { this.actions = actions; }這段代碼,在查看問題列表時,會根據(jù)問題的名稱,獲取問題在
wall的ObjectId,然后獲取當(dāng)前用戶對這個問題的權(quán)限,最后將權(quán)限的名稱添加到問題中。 -
完成上述操作后,使用
mall123打開通用問題頁面,添加新問題權(quán)限驗(yàn)證_1,我們可以看到如下界面:
似乎并沒有什么變化,但是當(dāng)我們分別用
mall123和admin123登錄后臺管理系統(tǒng),并抓取接口返回值會發(fā)現(xiàn)存在區(qū)別- 使用
mall123登錄后,訪問http://localhost:8080/admin/issue/list接口,返回值如下:
{ "errno": 0, "data": { "total": 1, "pages": 1, "limit": 20, "page": 1, "list": [ { "id": 5, "question": "權(quán)限驗(yàn)證_1", "answer": "權(quán)限驗(yàn)證_1", "addTime": "2024-06-15 12:06:35", "updateTime": "2024-06-15 12:06:35", "deleted": false, "actions": [ "manage" ] } ] }, "errmsg": "成功" }- 使用
admin123登錄后,訪問http://localhost:8080/admin/issue/list接口,返回值如下:
{ "errno": 0, "data": { "total": 1, "pages": 1, "limit": 20, "page": 1, "list": [ { "id": 5, "question": "權(quán)限驗(yàn)證_1", "answer": "權(quán)限驗(yàn)證_1", "addTime": "2024-06-15 12:06:35", "updateTime": "2024-06-15 12:06:35", "deleted": false, "actions": [] } ] }, "errmsg": "成功" }很明顯能夠看到,
mall123用戶對問題權(quán)限驗(yàn)證_1擁有manage權(quán)限,而admin123用戶沒有任何權(quán)限。這說明我們已經(jīng)成功的在后端實(shí)現(xiàn)了細(xì)粒度的權(quán)限控制。 - 使用
-
修改
org.linlinjava.litemall.admin.web.AdminAftersaleController類- 移除
list方法上的@RequiresPermissions和@RequiresPermissionsDesc注解;此舉是為了讓所有用戶都有權(quán)限去調(diào)用list接口,查詢所有管理用戶的基本信息 - 添加2個接口:
@GetMapping("/listObjPermission") // 獲取object的權(quán)限 public Object list(String username, @RequestParam String objectName) { // 獲取object的id final ObjectDto objectDto = objectImpl.get(objectName, 1L); // 獲取該對象被授予給哪些用戶哪些權(quán)限 final List<PermissionDto> permissionsForObject = rbacImpl.getPermissionsForObject(objectDto.getId(), 1L); return ResponseUtil.okList(permissionsForObject); } @PostMapping("/grantObjPermission") // 授予用戶object的權(quán)限 public Object grantObjPermission(@RequestBody GrantPermissions p){ final ObjectDto objectDto = objectImpl.get(p.getObjectName(), 1L); final ActionDto actionDto = actionImpl.get(p.getActionName(), 1L); // 暫時沒有batch接口,所以只能一個一個的授予 for (Long subjectId : p.getSubjectIds()) { final PermissionDto permissionDto = new PermissionDto(); permissionDto.setSubjectId(subjectId); permissionDto.setSubjectType(p.getSubjectType()); permissionDto.setObjectId(objectDto.getId()); permissionDto.setActionId(actionDto.getId()); permissionDto.setDomainDto(new DomainDto(1L)); permissionDto.setCreator(getUserId()); permissionDto.setMender(getUserId()); permissionDto.setEffect(EffectTypeEnum.ALLOW); permissionDto.setStatus(PermissionStatusEnum.DEFAULT); permissionDto.setType(PermissionTypeEnum.DEFAULT); permissionDto.setPriority(PriorityEnum.HIGHEST); rbacImpl.addPermissionForSubject(permissionDto); } return ResponseUtil.ok(); }- 添加
GrantPermissions類作為返回對象:
package org.linlinjava.litemall.db.domain; import com.github.moruke.wall.common.enums.SubjectTypeEnum; import java.util.List; public class GrantPermissions { private List<Long> subjectIds; private SubjectTypeEnum subjectType; private String objectName; private String actionName; public List<Long> getSubjectIds() { return subjectIds; } public void setSubjectIds(List<Long> subjectIds) { this.subjectIds = subjectIds; } public SubjectTypeEnum getSubjectType() { return subjectType; } public void setSubjectType(SubjectTypeEnum subjectType) { this.subjectType = subjectType; } public String getObjectName() { return objectName; } public void setObjectName(String objectName) { this.objectName = objectName; } public String getActionName() { return actionName; } public void setActionName(String actionName) { this.actionName = actionName; } } - 移除
接下來我們將修改前端,我們可以將問題的操作按鈕根據(jù)用戶的權(quán)限進(jìn)行顯示或隱藏,并且擁有權(quán)限的用戶可以將問題的權(quán)限下放給其他用戶。
2.2 修改前端
因?yàn)楸救饲岸怂接邢?,所以最終呈現(xiàn)的頁面可能不太美觀,但是功能是可以正常使用的。
-
打開
litemall-admin/src/views/mall/issue.vue文件,找到mall_issue.table.actions,將已有的兩個按鈕替換為如下代碼:<el-button v-if="scope.row.actions.includes('manage')" type="primary" icon="mini" @click="handleUpdate(scope.row)">{{ $t('app.button.edit') }}</el-button> <el-button v-if="scope.row.actions.includes('manage')" type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('app.button.delete') }}</el-button> <el-button v-if="scope.row.actions.includes('manage')" type="primary" size="mini" @click="handleGrant(scope.row)">{{ $t('app.button.permission') }}</el-button>這段代碼,會根據(jù)當(dāng)前用戶對問題的權(quán)限,顯示或隱藏操作按鈕。
修改完成后我們打開頁面,可以看到多出的授權(quán)按鈕
再切換成
admin123用戶登錄,我們可以看到操作下所有的按鈕都消失了
我們達(dá)成了根據(jù)用戶權(quán)限顯示或隱藏操作按鈕的目的。
- 打開
litemall-admin/src/views/mall/issue.vue文件,在methods中添加handleGrant方法:
handleGrant(row) {
this.permissionDialogFormVisible = true
this.currentRow = row
listAdmin(this.listQuery).then(response => {
this.allUser = response.data.data.list
})
this.listObjPermission.objectName = row.question
listObjPermission(this.listObjPermission).then(response => {
this.assignedUser = response.data.data.list.map(item => item.subjectId)
})
},
這段代碼,會在點(diǎn)擊授權(quán)按鈕時,會查詢所有用戶和已有權(quán)限的用戶。
- 打開
litemall-admin/src/views/mall/issue.vue文件,在最后一個div的最后添加如下內(nèi)容:
<el-dialog :visible.sync="permissionDialogFormVisible" :title="$t('sys_role.dialog.permission')">
<el-tree
ref="tree"
:data="allUser"
:default-checked-keys="assignedUser"
show-checkbox
node-key="id"
highlight-current
>
<span slot-scope="{ node, data }" class="custom-tree-node">
<span>{{ data.label }}</span>
<el-tag v-if="data.id" size="mini">{{ data.username }}</el-tag>
</span>
</el-tree>
<div slot="footer" class="dialog-footer">
<el-button @click="permissionDialogFormVisible = false">{{ $t('app.button.cancel') }}</el-button>
<el-button type="primary" @click="updatePermission">{{ $t('app.button.confirm') }}</el-button>
</div>
</el-dialog>
在data中添加如下內(nèi)容:
allUser: [],
assignedUser: [],
permissionDialogFormVisible: false,
listObjPermission: {
objectName: undefined
},
currentRow: {},
grantObjPermission: {
objectName: undefined,
subjectIds: undefined,
subjectType: 'USER',
actionName: 'manage'
}
在methods中添加如下內(nèi)容:
updatePermission(row) {
const checkedNodes = this.$refs.tree.getCheckedNodes()
this.grantObjPermission.subjectIds = checkedNodes.map(item => item.id)
this.grantObjPermission.objectName = this.currentRow.question
grantObjPermission(this.grantObjPermission).catch(() => {
this.$notify.error({
title: '失敗',
message: '更新權(quán)限失敗'
})
})
this.permissionDialogFormVisible = false
}
都完成后,我們重新加載頁面并點(diǎn)擊授權(quán)按鈕,此時會彈出一個對話框,并獲取所有用戶并將已有權(quán)限的用戶選中。

當(dāng)我們點(diǎn)擊確定按鈕時,會將選中的用戶授予manage權(quán)限。這里我們將所有用戶都選中,然后點(diǎn)擊確定按鈕,并切換成admin123用戶登錄:

現(xiàn)在,admin123用戶也可以對問題進(jìn)行操作了。
到此,我們已經(jīng)完成了對問題的細(xì)粒度權(quán)限控制,以及對問題權(quán)限的下放。擁有manage權(quán)限的用戶可以對問題進(jìn)行操作,而沒有manage權(quán)限的用戶只能查看問題。
3. 總結(jié)
關(guān)于快速接入wall-auth先到這里,我們通過對litemall項(xiàng)目的改造,實(shí)現(xiàn)了對問題的細(xì)粒度權(quán)限控制,以及對問題權(quán)限的下放。這里只是一個簡單的示例,實(shí)際項(xiàng)目中可能會更加復(fù)雜,但是思路是一樣的,只需要按照這個思路進(jìn)行修改,就可以實(shí)現(xiàn)細(xì)粒度的權(quán)限控制。
大家可以根據(jù)自己的需求進(jìn)行修改,如果有什么問題,歡迎在評論區(qū)留言,我會盡力解答。也歡迎在wall項(xiàng)目中提issue,我會盡快解決。
也歡迎大家一起多提PR,參與開發(fā),共同打造Great Wall。
4. 留作業(yè)
- 我們只錯了創(chuàng)建問題時授予
manage權(quán)限,但是沒有在刪除問題時撤銷manage權(quán)限,這里留作業(yè)給大家,大家可以嘗試在刪除問題時撤銷manage權(quán)限。 - 當(dāng)對同一個問題授予了多個用戶
manage權(quán)限時,會出現(xiàn)什么情況?
上面的作業(yè),大家可以嘗試完成,我也會在后續(xù)文章中給出答案。


