1. 准备工作
在上一篇文章中,我们介绍了wall-auth的基本使用,本篇文章将介绍如何快速接入wall-auth完成权限管理。 因为我没有别的Demo项目,所以我就直接以后端 作为关键字在github进行搜索,选择了排在第一的Java项目linlinjava/litemall,这个项目是一个商城项目,我将在这个项目的基础上进行接入。
shell
# clone项目
git clone https://github.com/linlinjava/litemall
cd litemall
# 将项目中的初始化sql文件导入数据库
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
# 编译后端服务并启动
mvn clean install -DskipTests -T 1C
java -Dfile.encoding=UTF-8 -jar litemall-all/target/litemall-all-0.1.0-exec.jar
# 编译前端服务并启动
cd litemall-admin
npm install
npm run dev
接下来我们打开浏览器并输入用户名密码admin123
/admin123
登录后台管理系统,我们可以看到如下界面:
这就代表我们正常启动了litemall
项目,接下来我们将在这个项目中接入wall-auth。
接入wall-auth
-
在
litemall
项目中添加wall-auth
的依赖,打开litemall-db/pom.xml
文件,添加如下依赖:xml<dependency> <groupId>com.github.moruke</groupId> <artifactId>wall-auth</artifactId> <version>1.1.1-SNAPSHOT</version> </dependency>
-
在
litemall
项目中添加wall-auth
的配置,打开litemall-db/src/main/resources/application-db.yml
文件,添加如下配置:yamlspring: 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
目录下创建casbin
目录,并在casbin
目录下创建model.conf
文件,内容如下:ini[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
所需的表结构,将wall-auth/src/main/resources/v1.0.0/init_ddl.sql
文件内容导入litemall数据库。 -
修改
org.linlinjava.litemall.Application
类,使用以下内容替换同名注解:java@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
项目,只要我们能正常启动litemall
项目,就代表我们已经成功接入了wall-auth
。如果出现了类似异常:shell*************************** 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
。 之所以发生这个问题,是因为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
注解时,会出现同名冲突,导致Spring无法注入正确的Bean。
2. 修改litemall项目
我们选择对通用问题的前后端进行调整,以支持细粒度的权限控制。
不过在这开始前,我们先给予商场管理员 权限,以便我们能够正常的使用通用问题 。(这部分能力litemall已经实现,我们只需要将其授权给商场管理员即可;只不过litemall的权限管理尚未做到细粒度,只能选择整个模块进行授权,而不能对指定的数据进行授权。而我们要修改并实现的就是这个功能。)
使用admin123
登录后打开系统管理/角色管理 ,点击商场管理员 后的授权 按钮,我们可以看到如下界面:
请将商场管理 下通用问题 下发给商场管理员 :
授权成功后我们退出登录,使用mall123/mall123
登录后台管理系统,我们可以看到如下界面:
这样mall123
就可以正常的使用通用问题。
接下来我们将对通用问题 相关代码进行改造,以支持细粒度的权限控制。我们将会给每个问题都绑定上manage
权限,只有拥有manage
权限的用户才能对问题进行操作。而其余用户只能查看问题。而创建问题的用户将会被赋予manage
权限。
2.0 修改数据库
执行以下语句:
sql
-- 插入`manage`权限点:
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`表中的数据:
truncate table `litemall`.`litemall_issue`;
2.1 修改后端
-
创建问题时,授予当前用户
manage
权限。 打开org.linlinjava.litemall.admin.web.AdminIssueController
类,找到create
方法,添加如下代码(添加在return之前):java// 将问题作为Object添加到权限系统 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); // 授予当前用户对问题的所有权限 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);
javaprivate Long getUserId() { // 通过SecurityUtils获取当前用户 final LitemallAdmin principal = (LitemallAdmin) SecurityUtils.getSubject().getPrincipal(); return Long.valueOf(principal.getId()); }
这段代码,在创建问题时,会将问题作为
Object
添加到权限系统,然后授予当前用户对问题的manage
权限。 -
查看问题列表时,补充当前用户对每个问题所拥有的权限。 打开
org.linlinjava.litemall.admin.web.AdminIssueController
类,找到list
方法,添加如下代码(添加在return之前):java// 补齐当前用户对这些问题的权限 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
类,添加如下代码:javaprivate List<String> actions; public List<String> getActions() { return actions; } public void setActions(List<String> actions) { this.actions = actions; }
这段代码,在查看问题列表时,会根据问题的名称,获取问题在
wall
的ObjectId
,然后获取当前用户对这个问题的权限,最后将权限的名称添加到问题中。 -
完成上述操作后,使用
mall123
打开通用问题 页面,添加新问题权限验证_1
,我们可以看到如下界面:似乎并没有什么变化,但是当我们分别用
mall123
和admin123
登录后台管理系统,并抓取接口返回值会发现存在区别- 使用
mall123
登录后,访问http://localhost:8080/admin/issue/list
接口,返回值如下:
json{ "errno": 0, "data": { "total": 1, "pages": 1, "limit": 20, "page": 1, "list": [ { "id": 5, "question": "权限验证_1", "answer": "权限验证_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
接口,返回值如下:
json{ "errno": 0, "data": { "total": 1, "pages": 1, "limit": 20, "page": 1, "list": [ { "id": 5, "question": "权限验证_1", "answer": "权限验证_1", "addTime": "2024-06-15 12:06:35", "updateTime": "2024-06-15 12:06:35", "deleted": false, "actions": [] } ] }, "errmsg": "成功" }
很明显能够看到,
mall123
用户对问题权限验证_1
拥有manage
权限,而admin123
用户没有任何权限。这说明我们已经成功的在后端实现了细粒度的权限控制。 - 使用
-
修改
org.linlinjava.litemall.admin.web.AdminAftersaleController
类- 移除
list
方法上的@RequiresPermissions
和@RequiresPermissionsDesc
注解;此举是为了让所有用户都有权限去调用list
接口,查询所有管理用户的基本信息 - 添加2个接口:
java@GetMapping("/listObjPermission") // 获取object的权限 public Object list(String username, @RequestParam String objectName) { // 获取object的id final ObjectDto objectDto = objectImpl.get(objectName, 1L); // 获取该对象被授予给哪些用户哪些权限 final List<PermissionDto> permissionsForObject = rbacImpl.getPermissionsForObject(objectDto.getId(), 1L); return ResponseUtil.okList(permissionsForObject); } @PostMapping("/grantObjPermission") // 授予用户object的权限 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
类作为返回对象:
javapackage 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; } }
- 移除
接下来我们将修改前端,我们可以将问题的操作按钮根据用户的权限进行显示或隐藏,并且拥有权限的用户可以将问题的权限下放给其他用户。
2.2 修改前端
因为本人前端水平有限,所以最终呈现的页面可能不太美观,但是功能是可以正常使用的。
-
打开
litemall-admin/src/views/mall/issue.vue
文件,找到mall_issue.table.actions
,将已有的两个按钮替换为如下代码:vue<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>
这段代码,会根据当前用户对问题的权限,显示或隐藏操作按钮。 修改完成后我们打开页面,可以看到多出的
授权
按钮再切换成
admin123
用户登录,我们可以看到操作下所有的按钮都消失了
我们达成了根据用户权限显示或隐藏操作按钮的目的。 2. 打开litemall-admin/src/views/mall/issue.vue
文件,在methods
中添加handleGrant
方法:
javascript
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)
})
},
这段代码,会在点击授权
按钮时,会查询所有用户和已有权限的用户。 3. 打开litemall-admin/src/views/mall/issue.vue
文件,在最后一个div
的最后添加如下内容:
html
<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
中添加如下内容:
javascript
allUser: [],
assignedUser: [],
permissionDialogFormVisible: false,
listObjPermission: {
objectName: undefined
},
currentRow: {},
grantObjPermission: {
objectName: undefined,
subjectIds: undefined,
subjectType: 'USER',
actionName: 'manage'
}
在methods
中添加如下内容:
javascript
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: '更新权限失败'
})
})
this.permissionDialogFormVisible = false
}
都完成后,我们重新加载页面并点击授权
按钮,此时会弹出一个对话框,并获取所有用户并将已有权限的用户选中。
当我们点击确定
按钮时,会将选中的用户授予manage
权限。这里我们将所有用户都选中,然后点击确定
按钮,并切换成admin123
用户登录:
现在,admin123
用户也可以对问题进行操作了。
到此,我们已经完成了对问题的细粒度权限控制,以及对问题权限的下放。拥有manage
权限的用户可以对问题进行操作,而没有manage
权限的用户只能查看问题。
3. 总结
关于快速接入wall-auth
先到这里,我们通过对litemall
项目的改造,实现了对问题的细粒度权限控制,以及对问题权限的下放。这里只是一个简单的示例,实际项目中可能会更加复杂,但是思路是一样的,只需要按照这个思路进行修改,就可以实现细粒度的权限控制。 大家可以根据自己的需求进行修改,如果有什么问题,欢迎在评论区留言,我会尽力解答。也欢迎在wall项目中提issue,我会尽快解决。
也欢迎大家一起多提PR,参与开发,共同打造Great Wall。
4. 留作业
- 我们只错了创建问题时授予
manage
权限,但是没有在删除问题时撤销manage
权限,这里留作业给大家,大家可以尝试在删除问题时撤销manage
权限。 - 当对同一个问题授予了多个用户
manage
权限时,会出现什么情况?
上面的作业,大家可以尝试完成,我也会在后续文章中给出答案。