feat: 完成工作流 Public API 授权闭环
- 新增访问令牌工作流 API 全局授权与 Public Workflow API 权限断言 - 补齐 API Key 执行记录归属、状态查询与下线后不可恢复边界 - 增加管理端接口调用说明与访问令牌授权开关
This commit is contained in:
@@ -82,7 +82,7 @@ public class ChainEventListenerForSave implements ChainEventListener {
|
||||
record.setWorkflowJson(workflow.getContent());
|
||||
record.setStartTime(new Date());
|
||||
record.setStatus(state.getStatus().getValue());
|
||||
record.setCreatedKey(WorkFlowUtil.USER_KEY);
|
||||
record.setCreatedKey(WorkFlowUtil.getCreatedKey(chain));
|
||||
record.setCreatedBy(WorkFlowUtil.getOperator(chain).getId().toString());
|
||||
try {
|
||||
workflowExecResultService.save(record);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package tech.easyflow.ai.service;
|
||||
|
||||
import tech.easyflow.system.entity.SysApiKey;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 工作流 Public API 访问令牌授权服务。
|
||||
*/
|
||||
public interface WorkflowApiPermissionService {
|
||||
|
||||
/**
|
||||
* 工作流资源类型。
|
||||
*/
|
||||
String RESOURCE_TYPE_WORKFLOW = "WORKFLOW";
|
||||
|
||||
/**
|
||||
* 工作流 Public API 调用动作范围。
|
||||
*/
|
||||
String ACTION_SCOPE_INVOKE = "INVOKE";
|
||||
|
||||
/**
|
||||
* 按访问令牌维度开启或关闭工作流 Public API 调用授权。
|
||||
*
|
||||
* @param apiKeyId 系统访问令牌 ID
|
||||
* @param enabled 是否启用工作流 API 调用授权
|
||||
*/
|
||||
void replaceWorkflowApiEnabled(BigInteger apiKeyId, boolean enabled);
|
||||
|
||||
/**
|
||||
* 断言当前令牌具备工作流 Public API 调用权限。
|
||||
*
|
||||
* @param apiKey 原始访问令牌
|
||||
* @param requestUri 请求地址
|
||||
* @return 已校验通过的访问令牌实体
|
||||
*/
|
||||
SysApiKey assertWorkflowApi(String apiKey, String requestUri);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package tech.easyflow.ai.service.impl;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tech.easyflow.ai.service.WorkflowApiPermissionService;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.SysApiKey;
|
||||
import tech.easyflow.system.entity.SysApiKeyResource;
|
||||
import tech.easyflow.system.entity.SysApiKeyResourceMapping;
|
||||
import tech.easyflow.system.service.SysApiKeyResourceMappingService;
|
||||
import tech.easyflow.system.service.SysApiKeyResourceService;
|
||||
import tech.easyflow.system.service.SysApiKeyService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流 Public API 访问令牌授权服务实现。
|
||||
*/
|
||||
@Service
|
||||
public class WorkflowApiPermissionServiceImpl implements WorkflowApiPermissionService {
|
||||
|
||||
private static final String RESOURCE_TITLE = "工作流 API 调用";
|
||||
|
||||
private static final List<String> WORKFLOW_API_URIS = List.of(
|
||||
"/public-api/workflow/getByIdOrAlias",
|
||||
"/public-api/workflow/getRunningParameters",
|
||||
"/public-api/workflow/runAsync",
|
||||
"/public-api/workflow/getChainStatus",
|
||||
"/public-api/workflow/resume"
|
||||
);
|
||||
|
||||
@Resource
|
||||
private SysApiKeyService sysApiKeyService;
|
||||
@Resource
|
||||
private SysApiKeyResourceService resourceService;
|
||||
@Resource
|
||||
private SysApiKeyResourceMappingService mappingService;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void replaceWorkflowApiEnabled(BigInteger apiKeyId, boolean enabled) {
|
||||
if (apiKeyId == null) {
|
||||
throw new BusinessException("系统访问令牌不能为空");
|
||||
}
|
||||
SysApiKey apiKey = sysApiKeyService.getById(apiKeyId);
|
||||
if (apiKey == null) {
|
||||
throw new BusinessException("系统访问令牌不存在");
|
||||
}
|
||||
mappingService.removeScopedMappings(apiKeyId, RESOURCE_TYPE_WORKFLOW);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SysApiKeyResourceMapping> rows = new ArrayList<>(WORKFLOW_API_URIS.size());
|
||||
for (String uri : WORKFLOW_API_URIS) {
|
||||
SysApiKeyResource resource = ensureResource(uri);
|
||||
SysApiKeyResourceMapping row = new SysApiKeyResourceMapping();
|
||||
row.setApiKeyId(apiKeyId);
|
||||
row.setApiKeyResourceId(resource.getId());
|
||||
row.setResourceType(RESOURCE_TYPE_WORKFLOW);
|
||||
row.setActionScope(ACTION_SCOPE_INVOKE);
|
||||
rows.add(row);
|
||||
}
|
||||
if (!rows.isEmpty()) {
|
||||
mappingService.saveBatch(rows);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public SysApiKey assertWorkflowApi(String apiKey, String requestUri) {
|
||||
SysApiKey sysApiKey = sysApiKeyService.getSysApiKey(apiKey);
|
||||
SysApiKeyResource resource = getResource(requestUri);
|
||||
QueryWrapper wrapper = QueryWrapper.create()
|
||||
.eq(SysApiKeyResourceMapping::getApiKeyId, sysApiKey.getId())
|
||||
.eq(SysApiKeyResourceMapping::getApiKeyResourceId, resource.getId())
|
||||
.eq(SysApiKeyResourceMapping::getResourceType, RESOURCE_TYPE_WORKFLOW)
|
||||
.isNull(SysApiKeyResourceMapping::getResourceTargetId)
|
||||
.eq(SysApiKeyResourceMapping::getActionScope, ACTION_SCOPE_INVOKE);
|
||||
if (mappingService.count(wrapper) == 0) {
|
||||
throw new BusinessException("该apiKey无权限调用工作流 API");
|
||||
}
|
||||
return sysApiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工作流 Public API 资源。
|
||||
*
|
||||
* @param requestInterface 请求地址
|
||||
* @return API 资源
|
||||
*/
|
||||
private SysApiKeyResource getResource(String requestInterface) {
|
||||
QueryWrapper wrapper = QueryWrapper.create()
|
||||
.eq(SysApiKeyResource::getRequestInterface, requestInterface);
|
||||
SysApiKeyResource resource = resourceService.getOne(wrapper);
|
||||
if (resource == null) {
|
||||
throw new BusinessException("该接口不存在");
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保工作流 Public API 资源已存在。
|
||||
*
|
||||
* @param requestInterface 请求地址
|
||||
* @return API 资源
|
||||
*/
|
||||
private SysApiKeyResource ensureResource(String requestInterface) {
|
||||
QueryWrapper wrapper = QueryWrapper.create()
|
||||
.eq(SysApiKeyResource::getRequestInterface, requestInterface);
|
||||
SysApiKeyResource resource = resourceService.getOne(wrapper);
|
||||
if (resource != null) {
|
||||
return resource;
|
||||
}
|
||||
resource = new SysApiKeyResource();
|
||||
resource.setRequestInterface(requestInterface);
|
||||
resource.setTitle(RESOURCE_TITLE);
|
||||
resourceService.save(resource);
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@ import java.math.BigInteger;
|
||||
public class WorkFlowUtil {
|
||||
|
||||
public final static String USER_KEY = "user";
|
||||
public final static String API_KEY = "API_KEY";
|
||||
public final static String WORKFLOW_KEY = "workflow";
|
||||
public final static String CREATED_KEY_MEMORY_KEY = "workflowCreatedKey";
|
||||
|
||||
public static String removeSensitiveInfo(String originJson) {
|
||||
JSONObject workflowInfo = JSON.parseObject(originJson);
|
||||
@@ -35,6 +37,17 @@ public class WorkFlowUtil {
|
||||
return cache == null ? defaultAccount() : (LoginAccount) cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工作流执行记录的执行人标识。
|
||||
*
|
||||
* @param chain 当前工作流执行链
|
||||
* @return 执行人标识
|
||||
*/
|
||||
public static String getCreatedKey(Chain chain) {
|
||||
Object value = chain.getState().getMemory().get(CREATED_KEY_MEMORY_KEY);
|
||||
return value == null ? USER_KEY : String.valueOf(value);
|
||||
}
|
||||
|
||||
public static LoginAccount defaultAccount() {
|
||||
LoginAccount account = new LoginAccount();
|
||||
account.setId(new BigInteger("0"));
|
||||
|
||||
@@ -26,6 +26,9 @@ public class SysApiKey extends SysApiKeyBase {
|
||||
@Column(ignore = true)
|
||||
private Boolean knowledgeShareEnabled;
|
||||
|
||||
@Column(ignore = true)
|
||||
private Boolean workflowApiEnabled;
|
||||
|
||||
@RelationOneToMany(selfField = "id", targetField = "apiKeyId", targetTable = "tb_sys_api_key_resource_mapping")
|
||||
private List<SysApiKeyResourceMapping> resourcePermissions;
|
||||
|
||||
@@ -52,4 +55,12 @@ public class SysApiKey extends SysApiKeyBase {
|
||||
public void setKnowledgeShareEnabled(Boolean knowledgeShareEnabled) {
|
||||
this.knowledgeShareEnabled = knowledgeShareEnabled;
|
||||
}
|
||||
|
||||
public Boolean getWorkflowApiEnabled() {
|
||||
return workflowApiEnabled;
|
||||
}
|
||||
|
||||
public void setWorkflowApiEnabled(Boolean workflowApiEnabled) {
|
||||
this.workflowApiEnabled = workflowApiEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user