Action與系統的權限控制剖析

        權限系統是多數應用系統必不可以少的子系統。曾經爲權限模型所困惑。今天突然想到用不同的Action代表不同的權限是實現權限控制是一個很好的做法。
我們知道,所謂的權限控制,就是一個“權限主體對於權限客體做了什麼操作”的問題。其中主體代表了權限系統的用戶,組,或者角色;而客體代表了權限系統中需要被保護的資源。顯然,客體(資源)+操作的組合就代表了權限。
使用MVC框架或者模式的時候,我們往往會有一個類,代表用戶的某一個操作。沒有框架的時候,可以是一個Servlet,有框架的時候,比如struts,webwork,就是一個Action。我們往往會每個用戶動作都會寫一個Action。而這個Action往往是諸如:ListUserAction,RemoveUserAction,CreateMessageAction,ModifyUserStatusAction等等,這裏的每一個Action都是一個用戶的操作,而不經意之間,這些個Action類就代表了一個資源和權限的組合,也就成爲了一個權限。同時,我們知道,在MVC框架中,請求一個Action類,要求把這個Action的mapping的名稱傳遞值FrontController Servlet,以便於讓Servlet知道請求的是哪一個Action,我們也可以方便的在ServletFilter或者Interceptor中獲得所請求的Action別名。這一切都好辦了,我們何不直接將Action別名作爲用戶的權限進行用戶權限設置,在每次請求的時候,得到請求的Action別名到權限Map中比對?
 

基於以上這種方式來實現權限控制,具體實現方式可以有所不同,做一些改進:

本質上一個Action代表了一個請求的URL地址,而B/S項目本質上也就是隻能根據URL來確定是否具備訪問權限,從原理上來說,也是合適的。但是Action代表的URL可能未必和邏輯意義上的權限功能模塊完全重合,既有可能大於,也有可能小於。

例如某個角色具備用戶管理權限,而這個用戶管理功能可能要對應六七個相關的Action,這就是邏輯意義上的權限大於Action,這種情況下在權限設置管理界面裏面初始化某角色的權限,則是一件非常累人的事情,爲了給一個角色增加一個功能權限,需要你分別設置六七個Action的權限。

再例如你控制一個簡單的工作流,那麼同一個Action ,會根據傳遞過去的不同的querystring來分派到不同的角色權限上,這就是同一個Action要針對不同的Role授予不同的權限了。

所以我覺得在Action上面再抽象出來一層Module,針對Module設置權限比較好。

 

我在webwork裏是這樣做的,首先定義一個接口:

代碼
  1. public interface Protected {   
  2.     public boolean hasPermission();   
  3. }   
<script type="text/javascript">render_code();</script>

 

然後用攔截器做檢查:

代碼
  1. public class PermissionInterceptor implements Interceptor {   
  2.     public String intercept(ActionInvocation invocation) throws Exception {   
  3.         Action action = invocation.getAction();   
  4.         if(action instanceof Protected) {   
  5.             if(!((Protected) action).hasPermission()){   
  6.                 return NOPERMISSION;   
  7.             }   
  8.         }   
  9.         return invocation.invoke();   
  10.     }   
  11. }   
<script type="text/javascript">render_code();</script>

 

任何需要做權限檢查的Action 實現這個接口即可,假設我們有一個管理員模塊,我們可以寫一個BaseAction:

代碼
  1. public class BaseAdminAction implements Protected {   
  2.     private SecurityService securityService;   
  3.   
  4.     public boolean hasPermission() {   
  5.         return securityService.hasPermission(RemoteUser.get(), "admin");   
  6.     }   
  7.   
  8.     public void setSecurityService(SecurityService securityService) {   
  9.         this.securityService = securityService;   
  10.     }   
  11. }   
<script type="text/javascript">render_code();</script>

 

在這裏採用setter注入SecurityService,具體的校驗邏輯都在SecurityService的實現裏面,你可以用簡單的查表法,也可以用很複雜的user-group-role-permission來實現。

在管理員模塊下的所有Action可以extends它就可以了:

代碼
  1. public class DeleteGroupAction extends BaseAdminAction {   
  2.     //......   
  3. }   
<script type="text/javascript">render_code();</script>

 

如果你需要更細粒度的控制,或者是一些特殊邏輯權限檢查,也可以選擇重寫這個方法

代碼
  1. public class DeleteGroupAction implements Protected {   
  2.     public boolean hasPermission() {   
  3.         return !group.getName().equals("ADMIN_GROUP") && securityService.hasPermission(RemoteUser.get(), "maintain_group");   
  4.     }   
  5. }   
<script type="text/javascript">render_code();</script>

 

這種做法就是Action組合好資源和操作,將其傳遞給SecurityService做檢查

其實我們既可以選擇直接在Action.hasPermission方法裏面做一些動作,也可以選擇注入不同的SecurityService實現,這樣就很靈活了。

 

 

robbin 寫道
再例如你控制一個簡單的工作流,那麼同一個Action ,會根據傳遞過去的不同的querystring來分派到不同的角色權限上,這就是同一個Action要針對不同的Role授予不同的權限了。

 

我怎麼覺得諸如queryString之類的權限控制不應在權限系統實現,基本上是個業務實現的問題。我覺得這個queryString往往是代表了“在什麼情況下”這個權限有效的問題,如果都考慮進入權限系統的話,權限系統會非常龐大,難以維護。這種權限生效的“situation”的問題,我往往做到業務邏輯層中去check。

Action之上抽象一層Module的做法是不錯。可是是不是可以考慮通過角色來解決諸如你這一類的問題呢?比如:用戶管理的,我可以設置一個用戶管理的角色,這一個角色可以授予執行createUser,removeUser,deleteUser權限,那麼,我的用戶是這個角色就可以咯。也就是說,是不是權限的粒度粗了,偶還是覺得用“資源+操作”去思考權限的粒度比較合適?比這個粒度粗的權限,就用角色來實現,比這個粒度細的權限就用業務實現。這樣權限系統比較適中。

 

的確,這是一個很好的入口,更好的入口是Service層.

對於權限,以我目前的理解,是要分成兩個問題:權限引擎和權限引入.

引擎就是要能提供一個可以切入應用邏輯的運算模塊,做到:
1.能將從業務邏輯中提取出權限邏輯.
2.能夠根據權限邏輯,對於當前操作進行運算,得到權限判斷結果.
引擎是個相對獨立的部分.

引入就是要能做到儘量透明的在系統中插入權限控制,必須:
1.儘量對業務透明.
2.提供合適的控制粒度.
引入可以採用AOP的Interceptor,只要在合理的層次上插入,就可以完成權限控制了.

在Action層插入,是一個方式.但是,因爲Action層次的信息仍然不夠豐富.更好的方式是在Service層次插入(對DomainObject操作).

jackyz     2005-01-21 16:56

 

robbin 寫道
早先我做過一個系統,就是採用在Service層實現權限控制,但是後來發現不如在Action層實現起來方便。一方面是因爲B/S應用中,客戶端的請求動作本來就是一個一個的HTTP GET/POST,要做到最準確的權限控制,則必須針對Action。另一方面來說,如果你在Service層做權限控制,意味着你在Action還要寫權限判定代碼,那麼就顯得非常煩瑣了。

 

如果要精確控制,必須要在比 Action 更細的層次做.舉例來說:

論壇的帖子顯示界面,如果當前用戶對當前帖子有刪除權限,則顯示刪除鏈接.否則,就不顯示.
當前的操作(Action)是顯示帖子,但是顯示鏈接的邏輯判斷需要對另外一個功能(刪除功能)進行判斷.這個判斷是業務的必須,沒有辦法省略.

 

代碼
  1. public class ViewArticle implements Action {   
  2.     public String execute() {   
  3.         ....   
  4.     }   
  5.     public boolean isDeleteAble() {   
  6.         ....   
  7.     }   
  8.     public boolean isEditAble() {   
  9.         ....   
  10.     }   
  11. }   
<script type="text/javascript">render_code();</script>

 

如果是對 Action 進行控制的話,恐怕就需要構造一個 URL 再行判斷了.此外,Role等信息也難於獲取.

ruby     2005-01-21 18:07

 

jackyz 寫道
robbin 寫道
早先我做過一個系統,就是採用在Service層實現權限控制,但是後來發現不如在Action層實現起來方便。一方面是因爲B/S應用中,客戶端的請求動作本來就是一個一個的HTTP GET/POST,要做到最準確的權限控制,則必須針對Action。另一方面來說,如果你在Service層做權限控制,意味着你在Action還要寫權限判定代碼,那麼就顯得非常煩瑣了。

 

如果要精確控制,必須要在比 Action 更細的層次做.舉例來說:

論壇的帖子顯示界面,如果當前用戶對當前帖子有刪除權限,則顯示刪除鏈接.否則,就不顯示.
當前的操作(Action)是顯示帖子,但是顯示鏈接的邏輯判斷需要對另外一個功能(刪除功能)進行判斷.這個判斷是業務的必須,沒有辦法省略.

 

代碼
  1. public class ViewArticle implements Action {   
  2.     public String execute() {   
  3.         ....   
  4.     }   
  5.     public boolean isDeleteAble() {   
  6.         ....   
  7.     }   
  8.     public boolean isEditAble() {   
  9.         ....   
  10.     }   
  11. }   
<script type="text/javascript">render_code();</script>

 

如果是對 Action 進行控制的話,恐怕就需要構造一個 URL 再行判斷了.此外,Role等信息也難於獲取.

 

如果這樣來寫ACTION,那Action也太不"純粹"了,包含了太多和當前action無關的邏輯,既然是要進行界面元素的顯示控制,就在界面上控制好了,比如用taglib,至於權限列表,保存在session,顯示界面元素時時取出對比一下,有此權限就顯示此界面元素,沒有就不顯示.

jackyz     2005-01-23 11:53

 

ruby 寫道
如果這樣來寫ACTION,那Action也太不"純粹"了,包含了太多和當前action無關的邏輯,既然是要進行界面元素的顯示控制,就在界面上控制好了,比如用taglib,至於權限列表,保存在session,顯示界面元素時時取出對比一下,有此權限就顯示此界面元素,沒有就不顯示.

 

確實不純粹.但是,對於實例級別的權限控制,似乎沒有更好的辦法.

首先,明確一下.這裏的需求是:實例級的權限控制(就是控制到誰對什麼資源[的哪一個實例]具有什麼操作).而不是功能級的權限控制(就是控制到誰對什麼資源[不管哪一個實例都]具有什麼操作).
實例級的權限控制,比如,A用戶可以刪除A帖子,但不能刪除B帖子.它與具體資源的實例相關.
功能級的權限控制,比如,A用戶可以刪除帖子(A用戶具有刪除帖子的權限,不管什麼帖子).它與具體資源的實例無關.
功能級的權限控制,與用戶和資源類型相關,對於特定的用戶來說,枚舉資源類型得到的列表是確定的,確實可以用Session保存列表,在顯示邏輯中處理.但是,實例級的權限控制,與具體的實例相關,對特定的用戶,不可能枚舉所有資源的實例.

這種情況下,只有Action的處理過程本身能夠確定對於特定實例(ArticleId=xxx)做出權限判斷.

從另一個角度來看,這裏的Action實際上是一個"交互層",即,它服務於顯示邏輯,顯示邏輯需要一個"是否可以刪除"的屬性,那麼它就應該提供,不管是從Session來取.還是從權限引擎來運算,這是另外一個問題.

這個做法應該說是合理的,但是,否有更好的方法,有待探討.

差沙     2005-01-23 14:42

在action中的攔截只能攔截功能型的權限
而把實例型的的權限校驗寫在action裏不是很好,action應該只是轉發

覺得應該在action的下一層來實現這個校驗,這樣權限校驗的耦合度還能第點,權限校驗也方便重用。

比方說你要顯示刪除的時候要進行權限校驗,真正刪除的時候也要進行同樣的校驗,這樣就應改把全校校驗分離出來,我也不懂,瞎想的。

差沙     2005-01-23 14:45

 

ruby 寫道
jackyz 寫道
robbin 寫道
早先我做過一個系統,就是採用在Service層實現權限控制,但是後來發現不如在Action層實現起來方便。一方面是因爲B/S應用中,客戶端的請求動作本來就是一個一個的HTTP GET/POST,要做到最準確的權限控制,則必須針對Action。另一方面來說,如果你在Service層做權限控制,意味着你在Action還要寫權限判定代碼,那麼就顯得非常煩瑣了。

 

如果要精確控制,必須要在比 Action 更細的層次做.舉例來說:

論壇的帖子顯示界面,如果當前用戶對當前帖子有刪除權限,則顯示刪除鏈接.否則,就不顯示.
當前的操作(Action)是顯示帖子,但是顯示鏈接的邏輯判斷需要對另外一個功能(刪除功能)進行判斷.這個判斷是業務的必須,沒有辦法省略.

 

代碼
  1. public class ViewArticle implements Action {   
  2.     public String execute() {   
  3.         ....   
  4.     }   
  5.     public boolean isDeleteAble() {   
  6.         ....   
  7.     }   
  8.     public boolean isEditAble() {   
  9.         ....   
  10.     }   
  11. }   
<script type="text/javascript">render_code();</script>

 

如果是對 Action 進行控制的話,恐怕就需要構造一個 URL 再行判斷了.此外,Role等信息也難於獲取.

 

如果這樣來寫ACTION,那Action也太不"純粹"了,包含了太多和當前action無關的邏輯,既然是要進行界面元素的顯示控制,就在界面上控制好了,比如用taglib,至於權限列表,保存在session,顯示界面元素時時取出對比一下,有此權限就顯示此界面元素,沒有就不顯示.

 

在頁面控制很危險的,可能你的頁面上控制不讓他顯示了,但是用戶能手動輸出地址呀,這不還是要轉到內部去校驗麼?

ruby     2005-01-23 15:08

 

引用
頁面控制很危險的,可能你的頁面上控制不讓他顯示了,但是用戶能手動輸出地址呀,這不還是要轉到內部去校驗麼?

這兒的"控制",控制的是頁面元素的顯示與否,而不是進行權限的控制,具體一點講,我們除了在action(iterceptor)或者更低一層(service)裏面進行權限控制以外,還要在頁面上管理相關頁面元素的顯示與否,目的不是爲了控制,而是爲了頁面的"乾淨",比如,一個不具備"刪除帖子"這個權限子的用戶登錄了以後,最好是不要把帖子的刪除功能的"鏈接"顯示出來,這樣頁面不就更人性一點了?(和我無關都不要給我顯示,顯示出來的都是我能操作的),如果他知道這個刪除功能的link,直接在瀏覽器裏面手寫提交,那麼在服務器端自然還有進一步的驗證,比如在Action裏面,比如在Service裏面,然後會再導航到相關信息提示或者出錯頁面.至少前面提到的實例一級的權限控制和功能一級的權限控制,我一般不會用到料度太細的實例一級,(比如對AA,BB用戶提交的帖子有刪除權限,而對CC這個領導提交的帖子卻是隻讀的)...倒是很想聽聽這方面的管理,實例一級的控制,那權限的粒度這麼小,管理不是非常麻煩?不過可能在電子政務和政府相關項目產品中會用得比較多一點吧

 

控制權限,不但在要頁面上,程序內部也必須控制,否則是相當危險的. 一般我的做法是在頁面上進行粗粒度的控制,在程序內容進行細粒度的控制.

根據權限控制顯示和根據權限控制執行在業務中,肯定都是必須的.缺少顯示控制則界面將不夠人性化.缺少執行控制則權限形同虛設.

這裏舉顯示的例子,是爲了表明,在Action的這個層次,除了一對一的執行控制以外,還有一對多的顯示控制.所以,在Action這個層次並不是進行權限對應的合理層次.

控制應該是基於Action之下的Service層次.比如:

如此的框架:

代碼
  1. public Interface Action {   
  2.   public String execute() throws ActionException;   
  3. }   
  4. public Interface Service {   
  5.   public void rule() throws ServiceExcception;   
  6.   pulblic Object flow() throws ServiceException;   
  7. }   
  8. public Class ServicePerformer {   
  9.   public boolean check(Service service) {   
  10.     boolean result = false;   
  11.     try {   
  12.       service.rule();   
  13.       result = true;   
  14.     } catch (ServiceException e) {   
  15.     }   
  16.     return result;   
  17.   }   
  18.   public Object perform(Service service) throws ServiceException {   
  19.     Object result = null;   
  20.     service.rule();   
  21.     result = service.flow();   
  22.     return result;   
  23.   }   
  24. }   
<script type="text/javascript">render_code();</script>

 

那麼,具體的一個實現可能類似:

代碼
  1. public class ViewAction implements Action {   
  2.   // 一個Action引用了兩個Service;   
  3.   private Service deletService;   
  4.   private Service detailService;   
  5.   // 服務執行器   
  6.   private ServicePerformer servicePerformer;   
  7.   // 顯示對象   
  8.   private ArticleDetail articleDetail;   
  9.   public String execute() throws ActionException {   
  10.     try {   
  11.       articleDetail = servicePerformer.perform(detailService);   
  12.       return SUCCESS;   
  13.     } catch (ServiceException e) {   
  14.       return ERROR;   
  15.     }   
  16.   }   
  17.   // 傳遞給顯示邏輯用的DTO   
  18.   public ArticleDetail getArticleDetail() {   
  19.     return articleDetail;   
  20.   }   
  21.   // 傳遞給顯示邏輯用的顯示控制用屬性   
  22.   public boolean isDeleteAble() {   
  23.     return servicePerformer.check(deleteService);   
  24.   }   
  25. }   
<script type="text/javascript">render_code();</script>

 

注意,以上代碼,僅表概念,並未精心設計.

供探討.

 

nihongye     2005-01-25 13:00

 

引用
在 url 中可以分辯 /deleteArticle.do 這個操作,但是並不能分辯當前用戶是否就是 articleId=123 這個帖子的作者.也就是說,如果不切入 Article 的 DomainObject 從 Article 中取出它的作者和當前用戶進行比對, 那麼,權限的判斷肯定是難於進行的.

 

那麼在 acegi 的方案中,對於這樣的需求是怎麼處理的呢?


1.acegi提供了可配置的voter,一個驗證請求交由多個voter進行處理,已經提供了roleVoter.可以加入自定義的voter來處理.

 

2.什麼是嵌入權限?

3.我想假如後臺需要爲多個前臺,或者是未來的前臺服務的時候,也纔有必要引入這種複雜性.

我也是看看,沒應用過,有什麼不對之處,還望海涵.

jjw     2005-01-25 14:21

不管在是在service還是action加權限驗證,實際是RBAC模塊是可以分離的,我在去年的時候完成了這個模塊,然後項目的時候只要把這個模塊加進去就可以了。如果實際項目是可以通過url來控制權限的,那麼就可以不用寫一行代碼。 在RBAC模塊裏完全可以做到很細的控制。
資源概念(可以是url,還有剛纔看到有人說.do後面的參數那不到,參數當然可以拿的的,只不過url被切分成兩部分而已,一部分就是參數.可以通過getQueryString得到)
資源就是想要的到的最終物質,我們可以給每一個資源定義一個權限,也可以給某一類資源定義一個權限,而web項目 url的tree特別容易用來管理資源。資源是一棵樹,如果你有管理樹根的權限那麼就有了這顆樹的所有權限。
權限概念
權限是對資源的一種保護訪問.用戶要訪問A資源前提是用戶必須有A資源的訪問權限.
角色概念
實事上我們不會直接把權限賦予給用戶,而是通過角色來賦予給用戶,因爲用戶擁有某一種權限是因爲用戶扮演着某一種角色。
A是個經理,他管理着B公司,他擁有b,c,d的權限。實際是不是A有這個權限,而是因爲Abo是經理。因爲經理擁有b,c,d權限
所以很顯然在權限劃分上,我們會把權限賦予給某一個角色,而不是賦予給個人。這樣帶來的好處是
如果公司換了經理,那麼只要再聘用一個人來做經理就可以了,而不會出現因爲權限在個人手裏導致權限被帶走的情況

分組概念(分組也是一棵樹,用戶就是這裏的葉子)
只有角色是不夠的,B公司發現A有財務問題成立了一個財務調查小組,然後我們賦予了這個小組財務調查員的角色(注意是賦予小組這個角色).這樣這個小組的所有人員
都有財務調查的資格。而不需要給小組的每個人都賦予這個角色(實際上已經擁有了),分組概念也適合部門,因爲任何一個部門在公司裏或者社會上都在扮演着一個泛的角色。
用戶
用戶一定是屬於某一個分組的,不存在不屬於分組的用戶.不過用戶可以直接扮演(獲得)角色,或者通過屬於的分組來得到角色
最後一個概念
判斷用戶有沒有訪問資源的權限就看這個用戶有沒有訪問這個資源的權限,也就是說分組,分部門,分角色最終是以權限來實現對資源的訪問控制

 

 

nihongye 寫道
1.acegi提供了可配置的voter,一個驗證請求交由多個voter進行處理,已經提供了roleVoter.可以加入自定義的voter來處理.

 

2.什麼是嵌入權限?

3.我想假如後臺需要爲多個前臺,或者是未來的前臺服務的時候,也纔有必要引入這種複雜性.

 

1.voter?在上述應用中,是否可以設想有一個voter,它所做的事情是否就是根據articleId從數據庫取出article.author,與當前用戶進行比對,如果相符就vote同意?

2.嵌入權限的意思就是,在系統設計之初不對權限做太多的設計,在系統的外部通過可配置的權限模塊來給系統引入權限邏輯.

3.這個前臺後臺沒有聽太懂.
象上述作例子論壇這樣的系統,沒有特定的後臺管理界面.版主,普通瀏覽者,匿名用戶等等,所有用戶都使用相同的界面.只是界面上顯示的內容不同.比如,在一個看帖界面中,版主能看到刪除鏈接,點擊該鏈接能執行刪除當前帖子的操作.但,匿名用戶看不見刪除鏈接,即使用戶自己拼出刪除鏈接,也不能執行刪除操作.

繼續探討.

nihongye     2005-01-25 20:42

1.是
2.同意.
3.後臺和前臺不是你說的意思.是指如:
ServiceBeanA代表後臺(相對穩定的一層).
ActionC,ActionD,ActionE代表前臺,或者URLC,URLD,URLE

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章