作为传统 ABAP 开发者,阅读 mcp-abap-abap-adt-api(以及底层 abap-adt-api)源码时,如果没先搞清 CSRF Token、stateful / stateless 会话模式等概念,很容易被底层复杂的 HTTP 封装绕晕。本文将通过Postman 实际调用 SAP ADT REST 服务,把这些抽象的底层概念变成"看得见、摸得着"的可观测实例。示例对象为程序 YJW_DEMO01。
1.获取x-csrf-token
服务URL:
http://ip:port/sap/bc/adt/compatibility/graph
HTTP方法:get
请求信息:header参数:X-CSRF-Token,值:Fetch
响应结果 :在 Response 的 Headers 中,会返回一个随机生成的 x-csrf-token 字符串

X-CSRF-Token 用来防 CSRF(跨站请求伪造)
它防的是什么
浏览器会自动带上已登录站点的 Cookie。如果只靠 Cookie 鉴权,恶意网页可以偷偷让你的浏览器对 SAP 发 POST(改对象、解锁、删东西),服务器会当成"你本人操作"。
CSRF Token 的思路是:
- 服务器先发一个不可预测的 token
- 写操作必须由客户端显式放进请求头带回
- 恶意第三方页面通常读不到你从 ADT 拿到的 token(同源策略)
- 没有正确 token → 拒绝(常见 403)
Cookie 证明"浏览器里有会话";Token 证明"这是知情的客户端主动发起的写请求"。
为什么 ADT 需要用到它
ADT 的 lock / unlock / 改源码都是会改系统状态的写操作。SAP 对这类请求校验
同一会话 Cookie + 匹配的 X-CSRF-Token才允许写操作
Cookie 证明你登录了;CSRF Token 证明这次写操作是你(或你的工具)发起的,不是别人借你的浏览器偷偷发的。
abap-mcp-adt的相关代码
GitHub - marcellourbani/abap-adt-api: Abap Developer Tools client · GitHub
路径:src/AdtHTTP.ts
方法: _request

2.加锁对象
代码路径
src/handlers/ObjectLockHandlers.ts
javascript
// 1. 在调用底层的 lock 之前,显式将客户端状态修改为 stateful
this.adtclient.stateful = session_types.stateful;
// 2. 然后调用 adt client 的 lock 方法加锁对象
const lockResult = await this.adtclient.lock(args.objectUrl, args.accessMode);
2.1 切换成有状态
在调用底层的 lock 之前,将客户端状态改为了 session_types.stateful
this.adtclient.stateful = session_types.stateful
本质上设置adt rest服务的请求头header的属性X-sap-adt-sessiontype为stateful,参考4.会话状态的说明
2.2 然后调用adt client的lock方法加锁对象
const lockResult = await this.adtclient.lock(args.objectUrl, args.accessMode);
SAP enqueue 成功后发的 会话锁令牌。后续 setObjectSource / unLock 都要带上它,证明持有这次锁。会话断开或 unLock 后就失效。
测试加锁测试程序YJW_DEMO01
服务URL:
http://ip:port/sap/bc/adt/programs/programs/yjw_demo01?_action=LOCK&accessMode=MODIFY
HTTP方法:post
输入步骤1获取到x-csrf-token
设置X-sap-adt-sessiontype成stateful,注意一定要stateful才触发加锁
响应结果 :接口成功后会返回一段 XML,解析后可以拿到一个加密的 lockHandle(会话锁令牌)

XML
<?xml version="1.0" encoding="utf-8"?>
<asx:abap version="1.0" xmlns:asx="http://www.sap.com/abapxml">
<asx:values>
<DATA>
<LOCK_HANDLE>0DDA6DC1A34A39BDA3AEB448769520EFA8736FE5</LOCK_HANDLE>
<CORRNR/>
<CORRUSER/>
<CORRTEXT/>
<IS_LOCAL>X</IS_LOCAL>
<IS_LINK_UP/>
<MODIFICATION_SUPPORT/>
<SCOPE_MESSAGES/>
</DATA>
</asx:values>
</asx:abap>
此时保持 Postman 连通状态,登录 SAP GUI 进入事务码 SM12 查看锁记录,会看到程序 YJW_DEMO01 已经被当前使用的 HTTP 帐号成功锁定了

adt.client.lock的底层代码
路径:

javascript
async function lock(h, objectUrl, accessMode = "MODIFY") {
(0, AdtException_1.ValidateObjectUrl)(objectUrl);
(0, AdtException_1.ValidateStateful)(h);
const qs = { _action: "LOCK", accessMode };
const response = await h.request(objectUrl, {
headers: {
Accept: "application/*,application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result"
},
method: "POST",
qs
});
const raw = (0, utilities_1.parse)(response.body);
const locks = (0, utilities_1.xmlArray)(raw, "asx:abap", "asx:values", "DATA");
return locks[0];
}
3.解锁对象
请求头 (Headers):
同样需要携带有效的 X-CSRF-Token 和 X-sap-adt-sessiontype: stateful。
URL 参数:必须将步骤 2 中获取的 lockHandle 作为 URL 参数传入,证明你持有这次锁的所有权。
HTTP 方法:POST
服务 URL:
http://ip:端口/sap/bc/adt/programs/programs/yjw_demo01?_action=UNLOCK&accessMode=MODIFY&lockHandle=0DDA6DC1A34A39BDA3AEB448769520EFA8736FE5

请求发送成功后,再次进入 SAP 后台刷新 SM12,该程序的锁记录已消失,资源成功释放。

路径
src/handlers/ObjectLockHandlers.ts

4.会话状态
X-sap-adt-sessiontype 属性是 SAP ABAP 核心开发团队在设计 ADT (ABAP Development Tools, 即 Eclipse 及 VS Code/Cursor 插件底层的 REST API) 时使用的内部/私有扩展 HTTP 头部字段。它的核心作用是指导 ABAP 后端的 ICF (Internet Communication Framework) 处理程序该如何维护用户的上下文(Roll Area)。
Stateless(无状态,默认):
每个 HTTP 请求到达后端后,系统会分配一个独立的内存上下文(Roll Area),请求处理完毕后立即释放。
Stateful(有状态):
当客户端通过 X-sap-adt-sessiontype: stateful 发送请求时,ABAP 后端会将其绑定到一个固定的工作进程上下文中。只要会话未超时或未显式关闭,全局变量、锁机制(如排他性编辑锁)都会保留在内存中。
adt客户端设置状态的代码
代码路径

经常在mcp的代码里看到设置状态的代码
this.adtclient.stateful = session_types.stateful;
本质上是设置adt rest服务的请求头header的属性X-sap-adt-sessiontype为stateful
const SESSION_HEADER = "X-sap-adt-sessiontype"

5.总结
-
X-CSRF-Token是前后端安全通信的门票。 -
X-sap-adt-sessiontype: stateful是在无状态的 HTTP 世界里拉起 ABAP 工作进程 Roll Area、维持ENQUEUE锁的隐形纽带。
理解了这两点,再去看MCP源码,思路就会豁然开朗。