企業級 BPM 部署常集中多流程於單一 jBPM 叢集,原生僅支援任務層級授權,缺乏流程定義/實例/歷史的檢視、啟動、刪除權限控制。本文詳細記錄基於 jBPM 4.3 核心 PVM (Process Virtual Machine) 的授權擴充實作:流程屬性宣告、ACL 持久化、命令攔截授權、查詢擴展、多身份來源支援。
starter (完整權限:啟動/刪除/檢視定義/實例/歷史) 與 user (唯讀檢視)。starter-users, starter-groups, user-users, user-groups,流程定義屬性以逗號分隔存放。any (所有使用者)、all (所有群組)、admin (超級管理員群組,隱含 all 權限)。user-* 視同 user-users="all";未指定 starter-* 視同所有流程使用者自動獲得 starter 角色。
<process key="AUTH_DEMO" name="需授權流程"
user-users="alice,bob" user-groups="analysts"
starter-users="charlie" starter-groups="managers">
<start name="s" g="10,10,48,48"><transition to="end"/></start>
<end name="end" g="10,100,48,48"/>
</process>
@Entity @Table(name="JBPM4_PROC_ACL")
public class ACL {
public static final String STARTER = "starter";
public static final String USER = "user";
@Id @GeneratedValue Long id;
String deploymentId; String processDefinitionId; String processDefinitionKey;
String principalId; // userId 或 groupId
String principalType; // "user" 或 "group"
String role; // "starter" 或 "user"
}
部署時由額外部署器 (部署器鏈最後) 解析流程屬性,批次建立 ACL 記錄。
@AuthorizedCommand(role, key) 標註需授權的命令方法 (如 getProcessDefinitionId())。
public void checkPermission(Command> cmd, EnvironmentImpl env) {
String userId = env.get(AuthIdentitySession.class).getAuthenticatedUserId();
for (Method m : cmd.getClass().getMethods()) {
AuthorizedCommand ann = m.getAnnotation(AuthorizedCommand.class);
if (ann != null) {
Object keyVal = m.invoke(cmd); // 取得 deploymentId/processDefinitionId...
List allowedUsers = queryAclUsers(keyVal, ann.role());
List allowedGroups = queryAclGroups(keyVal, ann.role());
if (!isAuthorized(env, allowedUsers, allowedGroups))
throw new AccessControlException(userId + " denied access to " + keyVal);
}
}
}
boolean isAuthorized(EnvironmentImpl env, List users, List groups) {
AuthIdentitySession idSess = env.get(AuthIdentitySession.class);
if (users.contains(AuthIdentitySession.ANY)) return true;
if (users.contains(idSess.getAuthenticatedUserId())) return true;
for (Group g : idSess.findGroupsByUser(idSess.getAuthenticatedUserId())) {
if (g.getId().equals(AuthIdentitySession.ADMIN) || groups.contains(g.getId()))
return true;
}
return false;
}
各命令子類化並加上 @AuthorizedCommand(role=STARTER, key=PROCESS_DEFINITION_ID) 註解。
// 部署/流程定義/流程實例/歷史查詢通用擴展
String userId = env.getAuthenticatedUserId();
List userGroups = env.get(IdentitySession.class).findGroupsByUser(userId);
hql.append(", ACL acl");
hql.append(" WHERE acl.deployment = deployment AND acl.role = 'starter'");
hql.append(" AND (acl.principalId IN (:userIds) OR acl.principalId IN (:groupIds))")
.setParameterList("userIds", buildUserList(userId))
.setParameterList("groupIds", buildGroupList(userGroups));
透過擴展查詢實作類並覆寫 hql() 方法注入條件。
啟動命令執行時,顯式將 currentUserId 寫入流程變數,查詢時可直接過濾。
AuthIdentitySession 介面定義 getAuthenticatedUserId(), findGroupsByUser(), setPrincipal()/reset()。IdentitySessionImpl、JBossIdmIdentitySessionImpl 解耦,支援 LDAP、SSO、自定義用戶存儲。SetPrincipalCommand/ResetPrincipalCommand 納入命令管線,確保環境可用時設定主體。
<bean name="jbpm.command.service.interceptors" class="java.util.ArrayList">
<constructor-arg>
<list>
<ref bean="retryInterceptor"/>
<ref bean="environmentInterceptor"/>
<ref bean="authorizationInterceptor"/> <!-- 新增 -->
<ref bean="transactionInterceptor"/>
</list>
</constructor-arg>
</bean>
<bean name="authorizationInterceptor" class="com.navteq.jbpm.auth.AuthorizationInterceptor"/>
<bean name="authIdentitySession" class="com.navteq.jbpm.auth.AuthIdentitySessionImpl"/>
服務實作類 (如 NavteqHistoryServiceImpl) 覆寫工廠方法回傳授權版查詢物件。
jbpm.user.wire.bindings.xml 註冊擴充 Bean,升級相容。此擴充已於生產環境穩定運行,支援數百流程定義、數萬實例的多租戶授權場景。