7
如何設計Data Level Access Control REST API? (/z/programming)

想請問依下各位大大的經驗~ 一般來說我們在開發Rest API 最基本會注意到 Authenication (你是誰) & Authority (你有沒有權限) 所以在API Level 我們可以設定誰有權限可以Call 這個API (如查詢用戶資訊) 但是有權限以後又想限制這人只能只能查詢自己相關的資料 那各位會如何設計呢? 下SQL時query 加上跟自己相關的where 條件?

IngramChen 積分 2 編輯於

後台權限我做的不多,沒接觸過大型企業用的系統。我自己的經驗就是你說的在 where 上加限制。(不過這本來就會下吧,不然怎麼查自己的相關記錄?)

我以前犯過一個錯誤,就是 authentication/authorization 都加了,但少做了個檢查:

// Rest API 層,處理 authentication 這塊
in getBalance(String username, String authenticationToken) {
  checkAuthentication(authenticationToken);
  return service.getBalance(username);
}

// 後台 service 層,有檢查這個用戶有沒有權限可拿 balance
int getBalance(String username) {
   checkUserCanGetBalance(username);
   return database.queryBlance(username);
}

上面的範例,Rest 層有驗 authentication,然後背後的 service 層有驗 該 user 有沒有取得 balance 的權限。而那個 database 的 query 就是下 where 條件。

我犯的錯是,我沒有檢查傳進的 username 和 authenticationToken 是同個人,所以 一個可通過驗證的人,變成可以傳不同 username 進來,取得所有人的 balance。

這個白吃的錯誤給大家借鏡...

howie 積分 1

正常來說where 加上限制,加限制是查詢使用者本身相關的資料 但是以上面的例子就是我有authentication 過了,然後我也有權限拿資料(access balance這個table) 但是如果使用者傳的是不同的username進來就GG了... 而且query越複雜就越麻煩...Orz.. 比如說這個使用者管理n間公司,n間公司又有m台機器 如果要對某台機器的資料設定,那要先確認這台機器是不是屬於這個使用這可以管理的 ,感覺就會把簡單的邏輯越寫越複雜...不知道有沒有好的design pattern

IngramChen 積分 5 編輯於

我也不知道很複雜的狀況下,有什麼簡單的寫法。有可能這件事本來就是這麼難。

我犯的錯誤,我後來用了兩種修正法:

//第一種,在 rest 層加上 annotation
int getBalance(
    @TargetUser String username, 
    @Auth String authenticationToken) {

這個寫法是我在 legacy 系統下加的,設計個 interceptor 之類的會攔截 method call,看到有 @TargetUser@Auth 這樣的 annotation 就會強制檢查是不是同一個人。然後會有另一個 unit test 去掃所有的 rest API,確定所有的 rest API 都有設定 security 相關的 annotation。

當然這是 legacy 系統,所以我不能改太多 signature ,只好用外加的方式處理。

後來,kaif 這個新系統,我就換了另一個方式來做:

int getBalance(String authenticationToken) {
  Auth auth = checkAuthentication(authenticationToken);
  return service.getBalance(auth);
}

// 後台 service 層, 參數是 type safe 的 Auth
int getBalance(Auth auth) {
   checkUserCanGetBalance(auth);
   return database.queryBlance(auth.getUsername());
}

第二種做法,你會看到我改用 type safe 的觀念來處理 authorization。service 層根本看不到 username 這種 純字串的值了,它被設計成只能傳 Auth ,這個 已經是個驗證過的人 的物件。這樣的設計自然去掉了之前的問題。 以後重用這個 service.getBalance(auth) 的人,它沒有別的選擇,只能想辦法生個 Auth 物件才能呼叫。

我想這兩個做法都是個方向,我會傾向用 type safe 的方式保護 API,讓授權這個隱含的性質突顯出來。 type safe 的做法,也許更複雜一點的會變這樣吧:

// service getBalance 的參數限制的更嚴格:
int getBalance(CompanyOwner companyOwner) {...}

// 而 companyOwner 則是推導自 Auth
CompanyOwner verifyOwnership(Auth auth, String company) {
   return database.queryCompany(
        auth.username, company);
}
Kros 積分 0
問個問題,像上述程式中
checkAuthentication(authenticationToken);

失敗是丟 exception 出來嘛?

IngramChen 積分 3

是丟 exception,因為驗證失敗是一種 意外 。 正常的 flow 是不會遇到的,所以也不該寫 if 之類的去擋

howie 積分 1

恩恩通常都是丟Exception出來~:P

kaif 積分 0

如果業務邏輯是user只能存取自己的東西,不會授權給別人,這樣還不錯。

IngramChen 積分 0

對喔,如果有授權給別人就更複雜了,ownership 又得加一層抽象...

kaif 積分 0

複雜認證授權應該可以考慮套spring security之類的東西?雖然我覺得她很肥。

howie 積分 2

spring security 也只是管到一般的Authenication & Authority

所以我可以再Rest API 或是Service Level加上 @Secured(AuthoritiesConstants.ADMIN)

但是依然無法解決Data level 的部分,所以只能自己來~:P

koji 積分 0

@PreAuthorize ? 但本質上也是自己來的意思,因為還是得加上判斷;而且漏寫就....。

swallow 積分 1

雖然跟本串無關,不過想亂入一下...

kaif有沒有考慮弄個追蹤功能還是特定串的收藏什麼的 @@>

像是這串蠻有意思的,不過時間久了沈掉就會覺得有點可惜...

如果可以收藏起來,等到遇到類似的問題可以回顧當初的討論什麼的XD

IngramChen 積分 2 編輯於

計畫中,會做 watch 的功能。

不過收藏大概不會做 (很重覆的功能,我們內部討論一陣子了)

swallow 積分 0 編輯於

<(_ _)>

howie 積分 0

不過我更好奇的是我很少看到相關的文章和討論,目前勉強可以看到比較相關的keyword 是 Data Contextual Access Control