feat(handoff): P1 后端 knowledge 兑现侧接入 Market token 核验/核销
P1(首条端到端 knowledge):knowledge-server 依赖 market-api,createKnowledgeBindingPrecheck 的 market_kb 来源在 precheck 阶段(token 明文仅此阶段持有、createBinding 的 BindReqVO 无 token) 调 MarketHandoffTokenApi.verify 服务端核验(属主/过期/一次性/owner-action),通过则 consume 核销 (消费换 session),替代此前"只存 hash 不验真"假安全。沿用 precheck @Transactional 事务保 verify/consume/落库原子。 注:执行版原写"createBinding 调 consume",自审修正为 precheck 阶段(token 明文仅此可得)。 验证:knowledge-server 编译(-am 连带刷新 meta-api 等)+ BcBoundaryArchTest 绿(knowledge→ market-api 合规、边界放行 -api)。端到端真实行为(拒伪造/过期 token + 核销 + DB)随 P1 前端 + handoff-knowledge.spec 真后端 e2e 覆盖。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a3b45ae6b5
commit
e5d2b52f20
@ -33,6 +33,12 @@
|
||||
<artifactId>muse-module-events-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- handoff 兑现:核验/核销 Market 一次性 token(MarketHandoffTokenApi,跨空间安全交接 P1) -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>muse-module-market-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>muse-module-infra-api</artifactId>
|
||||
|
||||
@ -5,6 +5,10 @@ import cn.iocoder.muse.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.muse.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.muse.module.knowledge.application.muse.facade.MuseKnowledgeWorkOwnerFacade;
|
||||
import cn.iocoder.muse.module.knowledge.controller.app.muse.vo.AppKnowledgeBindingVO;
|
||||
import cn.iocoder.muse.module.market.api.handoff.MarketHandoffTokenApi;
|
||||
import cn.iocoder.muse.module.market.api.handoff.dto.HandoffConsumeReqDTO;
|
||||
import cn.iocoder.muse.module.market.api.handoff.dto.HandoffVerifyReqDTO;
|
||||
import cn.iocoder.muse.module.market.api.handoff.dto.HandoffVerifyRespDTO;
|
||||
import cn.iocoder.muse.module.knowledge.dal.dataobject.muse.MuseKnowledgeBindPrecheckDO;
|
||||
import cn.iocoder.muse.module.knowledge.dal.dataobject.muse.MuseKnowledgeBindingDO;
|
||||
import cn.iocoder.muse.module.knowledge.dal.dataobject.muse.MuseKnowledgeCommandDO;
|
||||
@ -61,6 +65,8 @@ public class MuseKnowledgeBindingService {
|
||||
private MuseKnowledgeCommandService commandService;
|
||||
@Resource
|
||||
private MuseKnowledgeWorkOwnerFacade workOwnerFacade;
|
||||
@Resource
|
||||
private MarketHandoffTokenApi marketHandoffTokenApi;
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AppKnowledgeBindingVO.PrecheckRespVO createKnowledgeBindingPrecheck(Long loginUserId, String apiVersion,
|
||||
@ -87,6 +93,11 @@ public class MuseKnowledgeBindingService {
|
||||
AppKnowledgeBindingVO.ActionPolicyVO actionPolicy = actionPolicy(allowedPurposes, recheckReasons);
|
||||
LocalDateTime expiresAt = LocalDateTime.now().plusMinutes(30);
|
||||
String precheckId = "kb-bind-precheck-" + workId + "-" + safeCommandId(reqVO.getCommandId()) + "-" + UUID.randomUUID();
|
||||
// market_kb 来源:兑现前服务端核验 + 核销 Market handoff token(替代"只存 hash 不验真"假安全;消费 token 换 session)。
|
||||
// 沿用本方法 @Transactional 事务,verify/consume 与 precheck 落库原子(失败一并回滚)。token 明文仅 precheck 阶段持有。
|
||||
if ("market_kb".equals(reqVO.getSourceType())) {
|
||||
consumeMarketHandoff(reqVO, apiVersion, workId, precheckId);
|
||||
}
|
||||
String sourceSnapshotId = "source-" + reqVO.getSourceType() + "-" + reqVO.getSourceId() + "-v" + reqVO.getSourceVersion();
|
||||
Map<String, Object> summary = precheckSummary(reqVO, requestedPurposes, allowedPurposes, blockedPurposes,
|
||||
blockedReasons, actionPolicy, sourceSnapshotId);
|
||||
@ -255,6 +266,23 @@ public class MuseKnowledgeBindingService {
|
||||
sourceBindingProjectionMapper.insert(projection);
|
||||
}
|
||||
|
||||
/**
|
||||
* market_kb 来源:服务端核验 handoff token(属主/未过期/未消费/未取消 + owner-action 匹配)并核销(消费换 session)。
|
||||
* 替代此前"只对 token 取 hash 存档、从不回 Market 验真"的假安全——红线("不信任客户端 URL 参数")落地的关键一环。
|
||||
*/
|
||||
private void consumeMarketHandoff(AppKnowledgeBindingVO.PrecheckReqVO reqVO, String apiVersion, Long workId,
|
||||
String precheckId) {
|
||||
HandoffVerifyRespDTO verify = marketHandoffTokenApi.verify(
|
||||
new HandoffVerifyReqDTO(reqVO.getHandoffToken(), "knowledge", "bind"));
|
||||
if (!verify.valid()) {
|
||||
// token 不存在/非属主/已过期/已消费/owner-action 不匹配 → 阻断兑现,不写任何绑定事实
|
||||
throw new ServiceException(KNOWLEDGE_MARKET_HANDOFF_UNAVAILABLE);
|
||||
}
|
||||
// 核销 token(pending→completed);沿用调用方 @Transactional 事务,与 precheck 落库原子
|
||||
marketHandoffTokenApi.consume(new HandoffConsumeReqDTO(reqVO.getHandoffToken(), reqVO.getCommandId(),
|
||||
apiVersion, verify.status(), workId, precheckId));
|
||||
}
|
||||
|
||||
private void validatePrecheckReq(AppKnowledgeBindingVO.PrecheckReqVO reqVO) {
|
||||
if (!SOURCE_TYPES.contains(reqVO.getSourceType())) {
|
||||
throw new ServiceException(KNOWLEDGE_INVALID_PAGE_PARAM);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user