快速接入wall-auth完成權(quán)限管理

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

  1. 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>
    
  2. 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
    
  3. 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
    
  4. 初始化wall-auth所需的表結(jié)構(gòu),將wall-auth/src/main/resources/v1.0.0/init_ddl.sql文件內(nèi)容導(dǎo)入litemall數(shù)據(jù)庫。

  5. 修改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"})
    
  6. 讓我們重新啟動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.LitemallPermissionServiceorg.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.LitemallPermissionMappercom.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 修改后端

  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)限。

  2. 查看問題列表時,補(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ù)問題的名稱,獲取問題在wallObjectId,然后獲取當(dāng)前用戶對這個問題的權(quán)限,最后將權(quán)限的名稱添加到問題中。

  3. 完成上述操作后,使用mall123打開通用問題頁面,添加新問題權(quán)限驗(yàn)證_1,我們可以看到如下界面:

    似乎并沒有什么變化,但是當(dāng)我們分別用mall123admin123登錄后臺管理系統(tǒng),并抓取接口返回值會發(fā)現(xiàn)存在區(qū)別

    1. 使用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": "成功"
    }
    
    1. 使用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)限控制。

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

    1. 移除list方法上的@RequiresPermissions@RequiresPermissionsDesc注解;此舉是為了讓所有用戶都有權(quán)限去調(diào)用list接口,查詢所有管理用戶的基本信息
    2. 添加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();
     }
    
    1. 添加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)的頁面可能不太美觀,但是功能是可以正常使用的。

  1. 打開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)限顯示或隱藏操作按鈕的目的。

  1. 打開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)限的用戶。

  1. 打開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è)

  1. 我們只錯了創(chuàng)建問題時授予manage權(quán)限,但是沒有在刪除問題時撤銷manage權(quán)限,這里留作業(yè)給大家,大家可以嘗試在刪除問題時撤銷manage權(quán)限。
  2. 當(dāng)對同一個問題授予了多個用戶manage權(quán)限時,會出現(xiàn)什么情況?

上面的作業(yè),大家可以嘗試完成,我也會在后續(xù)文章中給出答案。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容