[Next Note] - 在 Next/React 使用 CASL 執行乾淨俐落的權限管理方案
Introduction & 前言
你的專案是否曾經碰過以下的需求:
- 這個畫面需要管理者才能刪除,一般的用戶不能刪除
- 我需要依照帳號角色來決定要顯示什麼
- 這麼多地方要寫判斷能不能顯示畫面,一堆
if-else
好醜啊,怎麼美化
如果剛好你有需要做權限的需求,剛好要碰到權限判斷,那 CASL 真的是完全符合你的選擇,
接下來我將記錄一篇怎麼在 Next 使用這套 Plugins 的過程。
CASL 可以在 Server端 及 Client端 安裝使用,如果你使用 Express、Koa、NestJS 或 前端三大框架(Vue、React、Angular) 皆可以使用,方法也差不多。
CASL 是一個權限管理套件,它提供了一種 DSL(Domain Specific Language),讓開發者可以使用類似自然語言的方式來定義權限控制規則。它支持將權限控制的邏輯集中管理,可以在多個地方重複使用。另外,CASL 還提供了可視化的能力,可以將權限控制規則轉換為圖形化的形式,便於開發者閱讀和維護。
Summary & 摘要
本篇文章預設學習前的基本條件需求:
- 需要會使用 Npm install
- 需要會 JavaScript(本篇文章以 React 生態框架下去紀錄)
本篇文章將會學到:
- 基礎認識 - CASL 可以做什麼
- 開始安裝 - 環境安裝及設定
- 使用方法 - 套件使用方法
備註:截至文章發表前,套件目前更新至 CASL V6 版本,有些文章搜尋後點擊跳轉的連結會跑到 v5 甚至 v3 去,需要特別注意一下
為何要寫這篇
老樣子還是需要說一下為什麼需要寫這篇文章,之前工作大部分接觸的後台系統,都不太需要做權限管理,有只是簡單的 超級管理員
跟 一般用戶
,
也不會有太多頁面需要做隱藏顯示的判斷,剛好前陣子自己寫的 Side Project 以及自己工作上真的開始碰到了後台系統需要做權限管控,
因為之前就有從前同事口中聽過 CASL 這套插件,所以當下二話不說就直接選擇這套。
But,因為在官方的範例中 React 使用的是 useContext 的方法(非 React 使用者可以忽略這段),筆者想研究如何把這套件丟到 Redux 中,畢竟可以丟到 Redux 就可以少寫一個 Context 的 Provider,也方便統一管理,何樂不為呢?所以這篇紀錄文就誕生了,希望能跟相同情境的使用者交流。
基礎認識
在前端專案上我們要實現權限檢查很可能充斥著以下這種代碼:
1 | if (user.role === 'admin' || user.id === article.authorId) { |
一來程式碼可能很雜亂,二來不好維護,邏輯一複雜了,可能就出現很多層的 if-else
,而對於權限來說始終脫離不了 主體、角色、權限…(RBAC) ,
而 CASL 提供了我們更細粒度的控制,CASL 可以讓你使用更直觀的 DSL(Domain-Specific Language) 來表示對於不同角色或者用戶的權限控制,而不是像 RBAC 一樣使用角色權限進行管理,如官方在 Define Rules 文內所述。
可以用上面的流程圖大概想一下,如果我們有好幾個頁面都需要判斷,是不是會充斥著一大堆的 if-else
,但這些程式碼都可以交由 CASL 來做到。
Basic Rules
先讓我們來一些基本認識吧!
基本上 CASL 的核心架構脫離不了四種規則,但通常我們比較常使用到的是 User Action 及 Subject。
User Action:通常用來放使用者操作的能力是什麼,例如 CRUD 。
Subject:用來檢查使用者操作的項目,通常會是一個 主題 或 實體,例如 Post 或者 article-list(下面會在說到用法)。
Fields:這個可以理解成 Subject 的補充,如果 Subject 是一個大項目,例如文章列表,那 Fields 可以想成小項目,例如文章列表裡面的 Title 或 Description。
Conditions: 使用這個可以更精準的匹配使用者是否符合該權限,如果用上一個 Fields 的條件來擴充,可以在使用這個方法去做更複雜的驗證,例如我想要達到使用者只能更新自己的文章。
綜合以上四中基本規則,你會得到以下的物件格式(Fields 及 Conditions 是非必填的):
1 | const usrAbility: [ |
根據上面的範例,代表我們賦予這個使用者的權限為可以閱讀(action: ‘Read’)文章列表(subject: ‘article-list’)、可以編輯(action: Update)作者 ID 為 1(conditions:{authorId: 1}) 文章裡的 Title(fields: [‘title’, ‘description’]) 及 Description。
當然這只是一個很簡單的範例,當中其實可以有更複雜的用法,像是 fields 可以使用 patterns,如上圖,但這邊就先不深入討論這個。
Define Rules
當我們賦予目標用戶擁有的權限後,該怎麼在套件上將這些權限做使用呢,CASL 的核心之一即是 能力(Ability)
而 CASL 定義能力的方式有三種,這邊我們使用第一種 defineAbility,另外兩種有興趣也可以參考官方網站 Define Rules 的介紹。
我們稍微修改一下官網提供的範例:
1 | // ability.js |
上方只是一個很基礎簡單的範例,但可以明顯發現我們能力都包裹在 Ability 之中,透過 definedAbility 提供的 can() 方法,去驗證我們目前定義的能力能不能使用,該方法定義的 Type 是 (method) can(...args: [action: any] | [action: any, subject: any, field?: string | undefined]): boolean
。
而創建 Ability 的方法也可以在包裹一層 function,透過傳入參數的方式,讓 conditions 裡面使用 user 傳遞進來的參數,例如上圖。
開始安裝
前面我們有提到,該篇文章將會以 Next 的角度下去紀錄,但其實透過上面的範例,普通的 JS 已經可以使用這個套件達到基本效果了。
提醒:這邊還是需要提醒一下,因為官方的範例是使用 useContext 的方式,而官方有提供一個 (method) can(...args: [action: any] | [action: any, subject: any, field?: string | undefined]): boolean
,因為該方法無法直接帶入 conditions,所以筆者還在研究如何帶入驗證,接下來的範例不會包含 Fields 及 Conditions 的部分,很抱歉!
首先我們到官網後,看到左側選擇你目前使用的環境,這邊筆者選擇 CASL React,接著內文其實都介紹得滿清楚的了,怎麼安裝及使用:
再次提醒,一定要注意看 CASL 的版本喔,如果你查看的官網為 v4 或 v5 版本,左上角下拉點開後不會有 v6,按照官網安裝方式現在裝的一定會是 v6 版本,網址需要自己輸入 https://casl.js.org/v6/en/package/casl-react 去查看 v6 的文件,因為 v6 已經棄用 import { Ability } form '@casl/ability';
了。
如果有在使用 ChatGPT 的工程師們,一定會發現它給你的範例都是使用這個方法。
React.js With useContext and CASL
>文章的最後會有一個 Demo 及 範例程式碼,筆者以自行理解的方式將 CASL 融入 Redux 之中,如果有誤,還請各路大神手下留情,底下可以留言告知筆者。
在 React 使用 CASL 官方的方法為 useContext ,我們先使用這種方式介紹:
- 首先創建一支檔案為 ability.ts(可是自己的情況改為 .js) ,筆者放在專案目錄
/utils/casl/ability.tsx
1 | import { defineAbility } from '@casl/ability'; |
- 接著修改 main.tsx 或 app.tsx,將 context 包裹著整個專案
1 | import ReactDOM from 'react-dom/client'; |
- 使用方式為兩種方式
1 | import { useEffect } from 'react'; |
兩種方式分別為直接使用核心 ability.can()
及使用套件幫你包裝好的 <Can />
,前面筆者有提到自行使用 Redux 定義之後,無法使用 ability.can()
帶入 conditions,這個組件可以幫你解決這個問題。
在不同的框架套件封裝的組件傳參方式都不同,例如 Vue 是 <Can do="read" :on="post" field="title">...</Can>
而 Angular 是 <div *ngIf="'create' | able: 'Post'" />
,但是目的都是要把 Basic Rules 那些當作條件傳遞進去給 ability 驗證是否合法,最後返回布林。
以上三個步驟是不是非常簡單呢?
Next.js With Redux and CASL
使用 Redux 主要原因為筆者覺得在最外層需要包裹兩層 Provider 有點醜,想要融合再一起,但筆者需要在聲明一次,截至文章發表前,筆者目前還沒有找到 ability.can()
可以傳遞 conditions 進去的方式。
- 我們跳過 Redux 的安裝,在 Redux 新增一個 authSlice.tsx
1 | import { createSlice } from "@reduxjs/toolkit"; |
- 接著就可以在專案上使用了
1 | import { selectAbility } from "@/store/reducers/authSlice"; |
如同前面一直提到的,筆者還在研究怎麼在 can 帶入 conditions,如果有大神知道的話還麻煩下面留言告知一下,非常感謝,筆者修改後會第一時間上來更新內容。
因為少了使用 import { Can } from '@casl/react';
這段,所以其實使用 Redux 的話可能需要自行封裝傳入 conditions 方法,這邊筆者會繼續爬文。
範例
按照慣例這邊附上實作好的 Demo:
Source Code: 傳送門
Demo: 傳送門
Conclusion & 結論
之前還沒有得知這套插件的時候還是一直使用 if-else
去判斷使用者的權限,有了這個套件,雖然免不了要在程式碼內寫一些客製化判斷,但基本上我們將邏輯都縮小到了 ability.js 裡 defineAbility()
的範圍內,算是方便統一維護及管理。
筆者藉著這次機會,順便第一次使用了 Next.js 來做一個簡易的後台,帳號密碼都是寫死在專案內的 API,畫面渲染可能會有點不佳,目前筆者還在持續學習當中,請各路大神手下留情,也希望這篇文章可以幫助到正在製作權限功能路上苦惱的工程師們。
備註:如果你是使用 Vue.js 也非常推薦查閱這篇文章 基于Vue.js开发的应用程序如何管理用户权限,內容非常扎實及完整,有興趣可以參考看看。
最後離題的推薦一下,使用 Next.js 除了解決路由的一些問題外,雖然滿多地方需要摸索的,但在部署上 Vercel 真的超級方便啊,只要將程式碼更新上 Github 後,就會自動進行部署,解決了需要自己實作 CD (Continuous Deployment) 的部分。
參考網站
- CASL
- 基于CASL的前端应用权限管理方案
- 基于Vue.js开发的应用程序如何管理用户权限
- ChatGPT <- 雖然幫助真的不大,給予的內容不是鬼打牆就是錯誤或者舊的寫法,但還是有給我一些思路上的幫助
- Vercel