快速接入wall-auth完成权限管理

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

  1. 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>
  2. litemall项目中添加wall-auth的配置,打开litemall-db/src/main/resources/application-db.yml文件,添加如下配置:

    yaml 复制代码
    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
  3. 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
  4. 初始化wall-auth所需的表结构,将wall-auth/src/main/resources/v1.0.0/init_ddl.sql文件内容导入litemall数据库。

  5. 修改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"})
  6. 让我们重新启动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.LitemallPermissionServiceorg.linlinjava.litemall.db.service.LitemallRoleService中,使用的@Resource替换为@Autowired。 之所以发生这个问题,是因为org.linlinjava.litemall.db.dao.LitemallRoleMappercom.github.moruke.wall.auth.dao.mapper.RoleMapper以及org.linlinjava.litemall.db.dao.LitemallPermissionMappercom.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 修改后端

  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);
    java 复制代码
        private Long getUserId() {
         // 通过SecurityUtils获取当前用户
         final LitemallAdmin principal = (LitemallAdmin) SecurityUtils.getSubject().getPrincipal();
         return Long.valueOf(principal.getId());
     }

    这段代码,在创建问题时,会将问题作为Object添加到权限系统,然后授予当前用户对问题的manage权限。

  2. 查看问题列表时,补充当前用户对每个问题所拥有的权限。 打开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类,添加如下代码:

    java 复制代码
     private List<String> actions;
    
     public List<String> getActions() {
         return actions;
     }
    
     public void setActions(List<String> actions) {
         this.actions = actions;
     }

    这段代码,在查看问题列表时,会根据问题的名称,获取问题在wallObjectId,然后获取当前用户对这个问题的权限,最后将权限的名称添加到问题中。

  3. 完成上述操作后,使用mall123打开通用问题 页面,添加新问题权限验证_1,我们可以看到如下界面:

    似乎并没有什么变化,但是当我们分别用mall123admin123登录后台管理系统,并抓取接口返回值会发现存在区别

    1. 使用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": "成功"
    }
    1. 使用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用户没有任何权限。这说明我们已经成功的在后端实现了细粒度的权限控制。

  4. 修改org.linlinjava.litemall.admin.web.AdminAftersaleController

    1. 移除list方法上的@RequiresPermissions@RequiresPermissionsDesc注解;此举是为了让所有用户都有权限去调用list接口,查询所有管理用户的基本信息
    2. 添加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();
     }
    1. 添加GrantPermissions类作为返回对象:
    java 复制代码
    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;
    }
    }

接下来我们将修改前端,我们可以将问题的操作按钮根据用户的权限进行显示或隐藏,并且拥有权限的用户可以将问题的权限下放给其他用户。

2.2 修改前端

因为本人前端水平有限,所以最终呈现的页面可能不太美观,但是功能是可以正常使用的。

  1. 打开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. 留作业

  1. 我们只错了创建问题时授予manage权限,但是没有在删除问题时撤销manage权限,这里留作业给大家,大家可以尝试在删除问题时撤销manage权限。
  2. 当对同一个问题授予了多个用户manage权限时,会出现什么情况?

上面的作业,大家可以尝试完成,我也会在后续文章中给出答案。

相关推荐
小_太_阳16 分钟前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
智慧老师25 分钟前
Spring基础分析13-Spring Security框架
java·后端·spring
搬码后生仔2 小时前
asp.net core webapi项目中 在生产环境中 进不去swagger
chrome·后端·asp.net
凡人的AI工具箱2 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
Lx3522 小时前
Pandas数据重命名:列名与索引为标题
后端·python·pandas
小池先生3 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端
百罹鸟3 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
小蜗牛慢慢爬行4 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
wm10435 小时前
java web springboot
java·spring boot·后端
小扳6 小时前
微服务篇-深入了解 MinIO 文件服务器(你还在使用阿里云 0SS 对象存储图片服务?教你使用 MinIO 文件服务器:实现从部署到具体使用)
java·服务器·分布式·微服务·云原生·架构