當今常見的BPM趨勢是集中化整個公司或公司內大部門的BPM執行這意味著單個BPM服務器(集群)運行著整個公司的許多流程定義這種方式的挑戰在 於雖然BPM引擎(包括jBPM)提供了對於任務訪問的授權但它們一般都不支持這些功能的授權流程定義的查看和刪除流程實例的啟動結束查看和刪除等在這篇文章中我們將描述如何對jBPM引擎進行擴展 (基於jBPM )來實現這一功能
整體實現方法
整個實現方式相當直接了當——對於每個流程定義引入一組可以授權的用戶/用戶組(類似任務定義)作用於定義實例和給定流程的歷史此外我們還想對給 定的用戶/用戶組支持多重授權級別——目前我們打算引入個角色starter和user這裡的starter是允許對流程定義/實例 /歷史進行任何操作的角色而user角色的權限僅限於查詢流程/歷史
這種方式的實現需要對jBPM進行以下改造
◆流程定義
給流程定義增加流程訪問權限
◆流程部署
擴展當前的流程部署器增加流程授權定義的解析和流程訪問列表的生成
引入額外的類/數據庫表存放每個流程定義的訪問權限
◆流程執行(Execution)
引入已授權命令(authorized command)——要求用戶經過授權才能執行的命令
修改現有的jBPM中我們期望基於當前用戶證書進行授權的部分這包括啟動結束和刪除流程實例以及刪除部署定義
修改現有的jBPM查詢考慮現有用戶的證書這包括部署和流程定義查詢流程實例查詢以及歷史流程實例活動和細節的查詢
除了以上更改我們還想擴展流程實例查詢好讓用戶可以通過指定某些流程變量的值來縮小查詢結果這種搜索的一個常見情況就是查詢由我啟動的流程為 了確保這種查詢總是可用我們更改了啟動流程實例命令的實現顯式地把當前用戶ID加到了流程變量值的集合中
最後為了支持多種用戶認證方法我們實現了一個自定義的身份會話它支持用程序來設置和訪問當前用戶的證書其目的在於把用戶證書(ID和參與的組) 的獲得和jBPM運行時對這種信息的使用分離開來
我們的實現利用了非常強大和靈活的jBPM 的配置機制它讓我們可以
通過擴展現有jBPM類最小化了自定義代碼的數量只實現我們擴展所需的額外功能
將我們的擴展實現成可以與jBPM 類庫一起使用的單獨jar包無需對現有庫進行任何改變
在深入我們的實現細節之前我們首先要討論一下我們大量使用的jBPM 的配置
jBPM 的配置機制
jBPM的基礎是流程虛擬機(PVM)[]它建立在自定義的依賴注入實現之上依賴注入由非常強大的基於XML的配置機制控制這種機制用於創建標簽和預定義接口相關的特定實現之間的綁定 (binding)
這種機制的核心是jbpmwirebindingsxml文件它描述了[] jBPM PVM的主要組件包括
◆基本類型
◆對象及引用
◆環境引用
◆Hibernate綁定
◆會話
◆服務
◆部署器
◆攔截器
◆等
該文件是jBPM分發包的一部分如果用戶想增加自己的綁定(binding)他可以創建jbpmuserwirebindingsxml描述 它們而不用修改jbpmwirebindingsxml文件
這兩個文件會被jBPM PVM在啟動時讀入並解析為定義在jbpmcfgxml中的基礎PVM執行(execution)配置而服務jbpmcfgxml一般會包含 多個部分描述了PVM執行的特定組件的配置
jBPM PVM由一組提供PVM功能的服務組成[]主要的PVM服務包括
◆倉儲服務提供一組查看和管理部署倉儲的方法
◆執行服務提供一組查看和管理運行中流程執行(execution)的方 法
◆管理服務提供一組查看和管理工作(job)的方法
◆任務服務提供一組查看和管理用戶任務(task)的方法
◆歷史服務提供一組訪問運行中和已完成流程執行的歷史信息的方法
這組可用服務和實現這些服務的類(使用前面說的綁定)被配置成流程引擎的上下文
服務執行被實現成一組命令(command)它們作為服務方法執行的一部分被調用命令的實際執行由命令服務控制
命令服務在命令服務上下文中被配置成一組攔截器實現橫切關注點環繞(around)命令調用(命令執行管線)缺省的jBPM分發包在命令執行管線中 攜帶了以下攔截器
◆重試(Retry)攔截器負責重試命令執行
◆環境(Environment)攔截器負責在必要時把jBPM上下文注入命 令執行中
◆事務(Transaction)攔截器負責介入命令調用的事務邊界劃分
攔截器是將jBPM移植到不同環境以及引入其他橫切關注點的核心機制
命令執行一般會利用環境它也是可配置的典型的環境組件有
◆倉儲會話
◆DB會話
◆消息會話
◆定時器會話
◆歷史會話
◆郵件會話
可以添加其他會話來擴展PVM的功能
最後部署管理器配置允許指定一組部署器它們依次執行把業務流程部署到PVM這種方法使得擴展流程定義可以通過實現額外的部署步驟完成無需覆蓋 jBPM分發包自帶的部署器
整個PVM的架構如圖示
圖 PVM架構
在流程定義中引入授權
我們在圖中看到可以給流程定義添加任意屬性利用這種擴展選項我們現在定義以下流程屬性描述授權策略
◆starterusers具有starter角色的用戶列表
◆startergroups具有 starter角色的組列表
◆userusers具有user角色的用戶列表
◆usergroups具 有user角色的組列表
每個屬性的值是逗號分隔的組/用戶id列表
圖 流程定義模式
此外我們還定義了一個特殊的用戶類型any和兩個用戶組all和admin任何用戶不論其真實ID是什麼都是any用 戶任何組不論其ID是什麼也都是all最後admin組的成員被認為是任意組的成員
流程授權定義由以下規則驅動
◆如果userusers和usergroups都未被指定則userusers=all
◆如果 starterusers和startegroups都未被指定則流程用戶被額外地分配starter角色
按照這個規則清單中的流程可以被任何人啟動和使用
<process package=comnavteqjbpm
key=NO_AUTHORIZATION
name=Test Authorization not required
version=
xmlns=>
<start g= name=start>
<transition to=end/>
</start>
<end g= name=end/>
</process>
清單 沒有授權信息的流程定義
清單的流程可以被mark或tomcat組中的任何人使用和啟動
<process package=comnavteqjbpm
key=AUTHORIZATION
name=Test Authorization Required
version=
xmlns=
userusers=mark
usergroups=tomcat>
<start g= name=start >
<transition to=end/>
</start>
<end g= name=end/>
</process>
清單 具有用戶授權信息的流程定義
我們引入了一個新類ACL針對給定流程 (processDefinitionIDprocessDefinitionKeyDeploymentID)它包含一個單獨的訪問列表(用戶或 組以及類型)同時還引入了相應的Hibernate定義
圖中清單的流程部署為具有兩個角色(user和starter)的用戶any創建了個ACL而在圖中清單的流程部署將創建 個用戶mark和組tomcat每個都具有個角色user和starter
圖 無授權信息的流程的ACL
圖 有用戶授權信息的流程的ACL
這些ACL的生成是通過引入額外的部署器完成的它將在標准jBPM部署器之後運行抽取上面描述的授權屬性為給定流程構建ACL
保護jBPM命令
我們采用了一種通用的方法來保護jBPM命令包括實現用於定義命令所需授權信息的自定義的注解以及處理這個注解的自定義的授權會話(命令攔截器)實現
授權注解(清單)可以指定所需的用戶角色和表示某個流程的方法
@Retention(value=RetentionPolicyRUNTIME)
@Target(value=ElementTypeMETHOD)
public @interface AuthorizedCommand {
/** Access type */
public String role();
String key();
}
清單 授權注解
對於某個流程用戶角色starter或user指向某個用戶應該擁有的角色[]由於不同命令既可以引用部署ID也可以引用流程ID或者流程鍵值因此注解支持多種指定鍵值的方式允許將合適的引用指定為鍵值
清單的授權攔截器檢查是否有命令的方法被授權注解修飾如果有則執行適當的查詢確定出哪些用戶和用戶組集合被授權給了這個命令然後檢查當前用戶是 否屬於他們
…………
@SuppressWarnings(unchecked)
public void checkPermission(Command<?> command EnvironmentImpl environment) {
environmentsetAuthenticatedUserId(environmentget(AuthorizationIdentitySessionclass)getAuthenticatedUserId());
for( Method method : commandgetClass()getMethods()) {
AuthorizedCommand sc = methodgetAnnotation(AuthorizedCommandclass);
if(sc != null){
logdebug(Checking Class based Secured Function);
String ID = environmentget(AuthorizationIdentitySessionclass)getAuthenticatedUserId();
Object value = null;
try {
logdebug(Checking authorization: + commandgetClass()getName());
Session session = environmentget(SessionImplclass);
value = methodinvoke(command (Object[])null);
Query uQ = sessioncreateQuery(userQueryget(sckey()))
setString(role scrole())setString(value(String) value);
Query gQ = sessioncreateQuery(groupQueryget(sckey()))
setString(role scrole())setString(value (String) value);
List<String> userIds = (List<String>)uQlist();
List<String> groups = (List<String>)gQlist();
if(!isAuthorized(environment userIds groups))
throw new AccessControlException(ID+ attempted access to ProcessDefinition #+value);
} catch (IllegalArgumentException e) {
logerror(Caught + IllegalArgumentExceptionclass e);
throw new AccessControlException(ID+ attempted access to ProcessDefinition #+value);
} catch (IllegalAccessException e) {
logerror(Caught + IllegalAccessExceptionclass e);
throw new AccessControlException(ID+ attempted access to ProcessDefinition #+value);
} catch (InvocationTargetException e) {
logerror(Caught + InvocationTargetExceptionclass e);
throw new AccessControlException(ID+ attempted access to ProcessDefinition #+value);
}
}
}
return;
}
……………………
public boolean isAuthorized(EnvironmentImpl env List<String> authorizedUserIds List<String> authorizedGroupIds) {
AuthorizationIdentitySession identitySession = envget(AuthorizationIdentitySessionclass);
if (antains(AuthorizationIdentitySessionANONYMOUS_USER_ID))
return true;
if (antains(identitySessiongetAuthenticatedUserId()) )
return true;
//check if any of userGroups is an authorized group if so then return true
List<Group> groups = identitySessionfindGroupsByUser(identitySessiongetAuthenticatedUserId());
for(Group group : groups){
String g = groupgetId();
//admin is allowed to execute any command
if(gequals(AuthorizationIdentitySessionADMINISTRATORS_GROUP))
return true;
if(auntains(g))
return true;
}
return false;
}
清單 授權攔截器
為了保護命令實現我們創建了一個新類它擴展了現有的命令增加了一個帶注解的方法(清單)返回給定命令可用的鍵值
@AuthorizedCommand(role = ACLSTARTER key = NavteqAuthorizationSessionPROCESSID)
public String getProcessDefinitionKey() {
return processDefinitionId;
}
清單 給啟動流程實例命令引入授權信息
根據所提議的方法我們對下列命令進行了注解
◆刪除部署
◆啟動流程實例
◆啟動最近的流程實例
◆結束流程實例
◆刪除流程實例
擴展查詢
引入授權意味著查詢結果應該只返回用戶被授權查看的信息[]這可以通過擴展現有查詢的where語句實現(清單)
//check authorization
String userId = EnvironmentImplgetCurrent()getAuthenticatedUserId();
List<Group> userGroups = EnvironmentImplgetCurrent()get(IdentitySessionclass)findGroupsByUser(userId);
hqlappend( + ACLclassgetName() + as acl );
appendWhereClause(acldeployment=deployment and acltype= +
ACLSTARTER +
hql);
appendWhereClause(((acluserId in +
UtilscreateHqlUserString(userId) +
) or +
(aclgroupId in +
UtilscreateHqlGroupString(userGroups) + ))
hql);
清單 為流程定義查詢提供授權支持的額外where語句
額外的where語句是通過擴展現有查詢實現和覆蓋hlq方法實現的
按照這種方法擴展了以下查詢
◆部署查詢
◆流程定義查詢
◆流程實例查詢
◆歷史流程實例查詢
◆歷史活動 查詢
◆歷史細節查詢
為了能夠增加字符串實例變量以縮小查詢結果我們還額外擴展了一個流程實例查詢
支持多種用戶管理方式
以上給出的實現依賴使用執行環境來獲得當前用戶ID和使用IdentitySession來獲得用戶組成員關系jBPM分發包提供了這個接口的個實現
◆IdentitySessionImpl基於jBPM的用戶/組數據庫
◆JBossIdmIdentitySessionImpl 基於JBoss Identity IDM組件
不同於大量依賴其他技術的實現對於我們的實現我們決定把用戶ID/組的獲取同這些信息的保存分離開來使之可以被其他的jBPM實現利用(圖)
圖 用戶管理實現
為了確保在設定和重新設定用戶證書的時候環境是可用的我們把這兩個操作實現成了命令(清單)這樣借助jBPM命令執行服務就可以正確設置執行環境
public static class SetPrincipalCommand extends AbstractCommand<Void> {
private static final long serialVersionUID = L;
private String userId;
private String[] groups;
public SetPrincipalCommand(String u Stringgroups) {
thisuserId=u; thisgroups=groups;
}
public Void execute(Environment environment) throws Exception {
environmentget(AuthorizationIdentitySessionclass)setPrincipal(userIdgroups);
return null;
}
}
public static class ResetPrincipalCommand extends AbstractCommand<Void> {
private static final long serialVersionUID = L;
public Void execute(Environment environment) throws Exception {
environmentget(AuthorizationIdentitySessionclass)reset();
return null;
}
}
把新命令和查詢引入到jBPM執行中
由於jBPM並沒有提供任何配置命令服務關系的支持為了能改變給定服務中的命令就必須使用調用新命令的新服務實現覆蓋舊實現清單給出了一個使用新命令服務覆蓋歷史服務的例子
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);
}
}
清單 在歷史服務中引入授權命令
總結
本文給出的這部分實現[]並沒有擴展JPDL而是擴展了被jBPM支持的所有語言都使用的JBoss PVM[]結果是這些語言都能夠使用這個實現
From:http://tw.wingwit.com/Article/program/Java/ky/201311/28771.html