程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用JBoss jBPM實現流程訪問和執行的授權

使用JBoss jBPM實現流程訪問和執行的授權

編輯:關於JAVA

當今常見的BPM趨勢是集中化整個公司或公司內大部門的BPM執行。這意味著,單個BPM服務器(集群) 運行著整個公司的許多流程定義。這種方式的挑戰在於,雖然BPM引擎(包括jBPM)提供了對於任務訪問 的授權,但它們一般都不支持這些功能的授權:流程定義的查看和刪除,流程實例的啟動、結束、查看和 刪除等。在這篇文章中,我們將描述如何對jBPM引擎進行擴展 (基於jBPM 4.3)來實現這一功能。

整體實現方法

整個實現方式相當直接了當——對於每個流程定義引入一組可以授權的用戶/用戶組(類似任務定義) ,作用於定義、實例和給定流程的歷史。此外,我們還想對給定的用戶/用戶組支持多重授權級別——目 前我們打算引入2個角色:“starter”和“user”。這裡的“starter”是允許對流程定義/實例 /歷史進 行任何操作的角色,而“user”角色的權限僅限於查詢流程/歷史。

這種方式的實現需要對jBPM進行以下改造:

流程定義

給流程定義增加流程訪問權限

流程部署

擴展當前的流程部署器,增加流程授權定義的解析和流程訪問列表的生成

引入額外的類/數據庫表,存放每個流程定義的訪問權限

流程執行(Execution)

引入已授權命令(authorized command)——要求用戶經過授權才能執行的命令

修改現有的jBPM中我們期望基於當前用戶證書進行授權的部分。這包括啟動、結束和刪除流程實例, 以及刪除部署定義。

修改現有的jBPM查詢,考慮現有用戶的證書。這包括部署和流程定義查詢、流程實例查詢,以及歷史 流程實例、活動和細節的查詢。

除了以上更改,我們還想擴展流程實例查詢,好讓用戶可以通過指定某些流程變量的值來縮小查詢結 果。這種搜索的一個常見情況就是查詢“由我啟動的”流程。為 了確保這種查詢總是可用,我們更改了 啟動流程實例命令的實現,顯式地把當前用戶ID加到了流程變量值的集合中。

最後,為了支持多種用戶認證方法,我們實現了一個自定義的身份會話,它支持用程序來設置和訪問 當前用戶的證書。其目的在於,把用戶證書(ID和參與的組) 的獲得和jBPM運行時對這種信息的使用分 離開來。

我們的實現利用了非常強大和靈活的jBPM 4的配置機制,它讓我們可以:

通過擴展現有jBPM類,最小化了自定義代碼的數量,只實現我們擴展所需的額外功能

將我們的擴展實現成可以與jBPM 4類庫一起使用的單獨jar包,無需對現有庫進行任何改變。

在深入我們的實現細節之前,我們首先要討論一下我們大量使用的jBPM 4的配置。

jBPM 4的配置機制

jBPM的基礎是流程虛擬機(PVM),它建立在自定義的依賴注入實現之上。依賴注入由非常強大的、基 於XML的配置機制控制,這種機制用於創建標簽和預定義接口相關的特定實現之間的綁定 (binding)。

這種機制的核心是jbpm.wire.bindings.xml文件,它描述了jBPM PVM的主要組件,包括:

基本類型

對象及引用

環境引用

Hibernate綁定

會話

服 務

部署器

攔截器

該文件是jBPM分發包的一部分。如果用戶想增加自己的綁定(binding),他可以創建 jbpm.user.wire.bindings.xml描述 它們,而不用修改jbpm.wire.bindings.xml文件。

這兩個文件會被jBPM PVM在啟動時讀入並解析,為定義在jbpm.cfg.xml中的基礎PVM執行(execution )配置而服務。jbpm.cfg.xml一般會包含 多個部分,描述了PVM執行的特定組件的配置。

jBPM PVM由一組提供PVM功能的服務組成。主要的PVM服務包括:

倉儲服務,提供一組查看和管理部署倉儲的方法

執行服務,提供一組查看和管理運行中流程執行(execution)的方 法。

管理服務,提供一組查看和管理工作(job)的方法

任務服務,提供一組查看和管理用戶任務(task)的方法。

歷史服務,提供一組訪問運行中和已完成流程執行的歷史信息的方法。

這組可用服務和實現這些服務的類(使用前面說的綁定)被配置成流程引擎的上下文。

服務執行被實現成一組命令(command),它們作為服務方法執行的一部分被調用。命令的實際執行由 命令服務控制。

命令服務在命令服務上下文中被配置成一組攔截器,實現橫切關注點,環繞(around)命令調用(命 令執行管線)。缺省的jBPM分發包在命令執行管線中 攜帶了以下攔截器:

重試(Retry)攔截器負責重試命令執行

環境(Environment)攔截器負責在必要時把jBPM上下文注入命 令執行中

事務(Transaction)攔截器負責介入命令調用的事務邊界劃分。

攔截器是將jBPM移植到不同環境以及引入其他橫切關注點的核心機制。

命令執行一般會利用環境,它也是可配置的。典型的環境組件有:

倉儲會話

DB會話

消息會話

定時器會話

歷史會話

郵件會話

可以添加其他會話來擴展PVM的功能。

最後,部署管理器配置允許指定一組部署器,它們依次執行,把業務流程部署到PVM。這種方法使得擴 展流程定義可以通過實現額外的部署步驟完成,無需覆蓋 jBPM分發包自帶的部署器。

整個PVM的架構如圖1示:

圖 1 PVM架構

在流程定義中引入授權

我們在圖2中看到,可以給流程定義添加任意屬性。利用這種擴展選項,我們現在定義以下流程屬性, 描述授權策略:

starter-users,具有“starter”角色的用戶列表

starter-groups,具有 “starter”角色的組列表

user-users,具有“user”角色的用戶列表

user-groups,具 有“user”角色的組列表

每個屬性的值是逗號分隔的組/用戶id列表。

圖 2 流程定義模式

此外,我們還定義了一個特殊的用戶類型——“any”和兩個用戶組——“all”和“admin”。任何用 戶,不論其真實ID是什麼,都是“any”用 戶。任何組,不論其ID是什麼,也都是“all”。最後, “admin”組的成員被認為是任意組的成員。

流程授權定義由以下規則驅動:

如果user-users和user-groups都未被指定,則user-users=”all”

如果 starter-users和starte-groups都未被指定,則流程用戶被額外地分配“starter”角色。

按照這個規則,清單1中的流程可以被任何人啟動和使用。

<process package="com.navteq.jbpm"
    key="NO_AUTHORIZATION"
    name="Test Authorization not required"  version="1"
    xmlns="http://jbpm.org/4.0/jpdl">
<start g="68,14,48,48" name="start" >
  <transition to="end"/>
</start>
<end g="78,383,48,48" name="end"/>
</process>

清單 1 沒有授權信息的流程定義

清單2的流程可以被mark或tomcat組中的任何人使用和啟動。

<process package="com.navteq.jbpm"   key="AUTHORIZATION"   name="Test  Authorization Required" version="1"   xmlns="http://jbpm.org/4.0/jpdl"   user- users="mark"   user-groups="tomcat">
<start g="68,14,48,48" name="start" >
  <transition to="end"/>
</start>
<end g="78,383,48,48" name="end"/>
</process>

清單 2 具有用戶授權信息的流程定義

我們引入了一個新類——ACL,針對給定流程 (processDefinitionID,processDefinitionKey, DeploymentID),它包含一個單獨的訪問列表(用戶或 組,以及類型);同時還引入了相應的Hibernate 定義。

圖3中,清單1的流程部署為具有兩個角色(“user”和“starter”)的用戶“any”創建了2個ACL; 而在圖4中,清單2的流程部署將創建4 個——用戶“mark”和組“tomcat”,每個都具有2個角色: “user”和“starter”。

圖 3 無授權信息的流程的ACL

圖 4 有用戶授權信息的流程的ACL

這些ACL的生成是通過引入額外的部署器完成的,它將在“標准”jBPM部署器之後運行,抽取上面描述 的授權屬性,為給定流程構建ACL。

保護jBPM命令

我們采用了一種通用的方法來保護jBPM命令,包括實現用於定義命令所需授權信息的自定義的注解, 以及處理這個注解的自定義的授權會話(命令攔截器)實現。

授權注解(清單3)可以指定所需的用戶角色和表示某個流程的方法。

@Retention(value=RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public @interface AuthorizedCommand {
  /** Access type */
  public String role();
  String key();
}

清單 3 授權注解

對於某個流程,用戶角色——“starter”或“user”——指向某個用戶應該擁有的角色。由於不同命 令既可以引用部署ID,也可以引用流程ID或者流程鍵值,因此注解支持多種指定鍵值的方式,允許將合適 的引用指定為鍵值。

清單4的授權攔截器檢查是否有命令的方法被授權注解修飾。如果有,則執行適當的查詢,確定出哪些 用戶和用戶組集合被授權給了這個命令,然後檢查當前用戶是 否屬於他們。

…………..
@SuppressWarnings("unchecked")
public void checkPermission(Command<?> command, EnvironmentImpl environment)  {

environment.setAuthenticatedUserId(environment.get (AuthorizationIdentitySession.class).getAuthenticatedUserId());
  for( Method method : command.getClass().getMethods()) {
  AuthorizedCommand sc = method.getAnnotation(AuthorizedCommand.class);
  if(sc != null){
   log.debug("Checking Class based Secured Function");
   String ID = environment.get (AuthorizationIdentitySession.class).getAuthenticatedUserId();
   Object value = null;
   try {
   log.debug("Checking authorization: " + command.getClass().getName());
   Session session = environment.get(SessionImpl.class);
   value = method.invoke(command, (Object[])null);
   Query uQ = session.createQuery(userQuery.get(sc.key())).
    setString("role", sc.role()).setString("value",(String) value);
   Query gQ = session.createQuery(groupQuery.get(sc.key())).
    setString("role", sc.role()).setString("value", (String) value);
   List<String> userIds = (List<String>)uQ.list();
   List<String> groups = (List<String>)gQ.list();
   if(!isAuthorized(environment, userIds, groups))
    throw new AccessControlException(ID+" attempted access to ProcessDefinition  #"+value);
   } catch (IllegalArgumentException e) {
   log.error("Caught " + IllegalArgumentException.class, e);
   throw new AccessControlException(ID+" attempted access to ProcessDefinition  #"+value);
   } catch (IllegalAccessException e) {
   log.error("Caught " + IllegalAccessException.class, e);
   throw new AccessControlException(ID+" attempted access to ProcessDefinition  #"+value);
   } catch (InvocationTargetException e) {
   log.error("Caught " + InvocationTargetException.class, e);
   throw new AccessControlException(ID+" attempted access to ProcessDefinition  #"+value);
   }
  }
  }
  return;
}
……………………..
public boolean isAuthorized(EnvironmentImpl env, List<String>  authorizedUserIds, List<String> authorizedGroupIds) {
  AuthorizationIdentitySession identitySession = env.get (AuthorizationIdentitySession.class);
  if (authorizedUserIds.contains(AuthorizationIdentitySession.ANONYMOUS_USER_ID))
  return true;
  if (authorizedUserIds.contains(identitySession.getAuthenticatedUserId()) )
  return true;
  //check if any of userGroups is an authorized group. if so then return  true
  List<Group> groups = identitySession.findGroupsByUser (identitySession.getAuthenticatedUserId());
  for(Group group : groups){
  String g = group.getId();
  // admin is allowed to execute any command 
    if(g.equals(AuthorizationIdentitySession.ADMINISTRATORS_GROUP))
   return true;
  if(authorizedGroupIds.contains(g))
   return true;
}
return false;
}

清單 4 授權攔截器

為了保護命令實現,我們創建了一個新類,它擴展了現有的命令,增加了一個帶注解的方法(清單5) ,返回給定命令可用的鍵值。

@AuthorizedCommand(role = ACL.STARTER, key =  NavteqAuthorizationSession.PROCESSID)
public String getProcessDefinitionKey() {
   return processDefinitionId;
}

清單 5 給啟動流程實例命令引入授權信息

根據所提議的方法,我們對下列命令進行了注解:

刪除部署

啟動流程實例

啟動最近的流程實例

結束流程實例

刪除流 程實例

擴展查詢

引入授權意味著查詢結果應該只返回用戶被授權查看的信息。這可以通過擴展現有查詢的where語句實 現(清單6)。

//check authorization
String userId = EnvironmentImpl.getCurrent().getAuthenticatedUserId();
List<Group> userGroups = EnvironmentImpl.getCurrent().get (IdentitySession.class).findGroupsByUser(userId);

hql.append(", " + ACL.class.getName() + " as acl ");
appendWhereClause("acl.deployment=deployment and acl.type='"
  + ACL.STARTER + "' ", hql);
appendWhereClause("((acl.userId in "
  + Utils.createHqlUserString(userId) + ") or " + "(acl.groupId in "
    + Utils.createHqlGroupString(userGroups) + ")) ", hql);

清單 6 為流程定義查詢提供授權支持的額外where語句

額外的where語句是通過擴展現有查詢實現和覆蓋hlq方法實現的。

按照這種方法,擴展了以下查詢:

部署查詢

流程定義查詢

流程實例查詢

歷史流程實例查詢

歷史活動 查詢

歷史細節查詢

為了能夠增加字符串實例變量,以縮小查詢結果,我們還額外擴展了一個流程實例查詢。

支持多種用戶管理方式

以上給出的實現依賴使用執行環境來獲得當前用戶ID和使用IdentitySession來獲得用戶組成員關系。 jBPM分發包提供了這個接口的2個實現:

IdentitySessionImpl,基於jBPM的用戶/組數據庫

JBossIdmIdentitySessionImpl, 基於JBoss Identity IDM組件

不同於大量依賴其他技術的實現,對於我們的實現,我們決定把用戶ID/組的獲取同這些信息的保存分 離開來,使之可以被其他的jBPM實現利用(圖5)。

圖 5 用戶管理實現

為了確保在設定和重新設定用戶證書的時候環境是可用的,我們把這兩個操作實現成了命令(清單7) ,這樣,借助jBPM命令執行服務就可以正確設置執行環境。

public static class SetPrincipalCommand extends AbstractCommand<Void>  {
  private static final long serialVersionUID = 1L;
  private String userId;
  private String[] groups;
  public SetPrincipalCommand(String u, String...groups) { this.userId=u;  this.groups=groups; }
  public Void execute(Environment environment) throws Exception {
  environment.get(AuthorizationIdentitySession.class).setPrincipal(userId,groups);
  return null;
  }
}

public static class ResetPrincipalCommand extends AbstractCommand<Void> {
  private static final long serialVersionUID = 1L;
  public Void execute(Environment environment) throws Exception {
  environment.get(AuthorizationIdentitySession.class).reset();
  return null;
  }
}

清單 7 設置用戶證書命令

把新命令和查詢引入到jBPM執行中

由於jBPM並沒有提供任何配置“命令——服務”關系的支持,為了能改變給定服務中的命令,就必須 使用調用新命令的新服務實現覆蓋舊實現。清單8給出了一個使用新命令服務覆蓋歷史服務的例子。

public class NavteqHistoryServiceImpl extends HistoryServiceImpl {

  @Override
  public HistoryActivityInstanceQuery createHistoryActivityInstanceQuery() {
  return new AuthorizedHistoryActivityInstanceQueryImpl(commandService);
  }

  @Override
  public HistoryDetailQuery createHistoryDetailQuery() {
  return new AuthorizedHistoryDetailQueryImpl(commandService);
  }

  @Override
  public HistoryProcessInstanceQuery createHistoryProcessInstanceQuery() {
  return newAuthorizedHistoryProcessInstanceQuery(commandService);
  }
}

清單 8 在歷史服務中引入授權命令

總結

本文給出的這部分實現並沒有擴展JPDL,而是擴展了被jBPM支持的所有語言都使用的JBoss PVM。結果 是,這些語言都能夠使用這個實現。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved