ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SAP ABAP 调用 HTTP 接口并实现 Token 认证全流程

SAP ABAP 调用 HTTP 接口并实现 Token 认证全流程 1024 这天圈子里总爱互道一句节日快乐做 ABAP 的老伙计尤其懂这个梗的分量。就在这种轻松的气氛里,我被问到最多的一类问题又冒了出来:SAP 系统里到底怎么去调一个外部的 HTTP 接口,而且对方还要求先用账号密钥换一个 token,后面的每一次请求都得把这个令牌带上,否则一律拒之门外。听着像两件事,本质上是一件事——先把身份认证这关过了,再谈业务数据。这篇就围绕 SAP ABAP 调用 HTTP 接口并用 token 登录这条主线,把我实际项目里怎么设计、怎么配置、怎么排错,从头到尾摊开讲。适合正在做外部系统集成、中台对接、或者第一次正经接触 IF_HTTP_CLIENT 的 ABAP 开发者,顾问朋友想搞明白 token 认证到底怎么回事也能看。1. 先想清楚:ABAP 调外部 HTTP 接口这件事难在哪1.1 场景拆解:谁在调谁,token 扮演什么角色先把场景说透。绝大多数需要 token 的接口,都是对方系统出于安全考虑,不让你直接拿账号密码去戳业务数据,而是设计成两步走。第一步,你带着身份凭证去敲一个专门的认证地址,对方核实无误后,丢给你一串有时效的字符串,这就是 token。第二步,你拿着这串令牌去调真正的业务接口,对方在报文头里一看,令牌合法且没过期,才把数据给你。这个模式的好处很实在。你的账号密码只在第一步出现,业务请求里根本不含它,即使报文被人截走,拿到的也只是一张会过期的临时通行证,损失可控。对我们 ABAP 这边来说,难点从来不在于发送一个 HTTP 请求本身,而在于三件事:令牌怎么安全地拿、怎么在后续请求里正确地带上、以及令牌过期了怎么优雅地续。很多人第一次做的时候,把这两步揉成一个程序写完就交差,结果对方一改认证方式,整个程序全废。所以我一般从一开始就把它拆成两个方法,认证归认证,业务归业务。还有一个容易被忽略的点:token 是有生命周期的。对方返回的 JSON 里通常带一个 expires_in 字段,单位是秒。你必须在它过期之前做判断,要么重新取一个,要么直接抛出明确的错误,千万别拿一个失效的令牌去反复敲业务接口,那只会招来一串 401,把对方的日志刷满,合作方看着也尴尬。1.2 技术选型:为什么是 IF_HTTP_CLIENT 而不是别的在 ABAP 里调 HTTP,路子其实不止一条。早年间有人用 CBO 的 RFC 绕,有人拼原生命令调外部工具,现在也有部分场景可以用内置的 HTTP 客户端封装。但真正稳、真正官方推荐、也真正被 SM59 管起来的,还是标准接口 IF_HTTP_CLIENT。它的价值在于:你不是在系统里凭空造一个连接,而是先通过事务码 SM59 把目标系统登记成一个目标(destination),然后代码通过这个目标去发起请求。这样做的好处非常实际。目标配置是跨客户端、可传输、可集中维护的,改一个主机名、换一个端口,不需要动代码,更不需要重新走一轮传输。目标里还能集中配置代理、SSL 证书、超时,权限也按照标准对象管控。换句话说,IF_HTTP_CLIENT 把连接往哪去、怎么去这些运维层面的事交给了 SM59,把发什么、收什么留给了代码,职责清晰。相反,如果你图省事,直接在代码里硬编码 URL 去创建客户端,短期看没问题,长期看就是灾难:生产环境和测试环境域名不同、证书不同、端口不同,你只能在代码里写一堆 IF 判断,维护起来想哭。所以我的原则很明确——能用目标走目标,只有在极少数特殊情况下才用 create_by_url 直接创建,而且 URL 也必须从配置表里读,不许写死。2. 动手前的准备:SM59、证书和权限2.1 用 SM59 把外部系统登记进来打开 SM59,新建一个连接,连接类型选 G,也就是连接到外部服务器的 HTTP 连接。这一步选的类型决定了后面你能配置哪些参数。填的时候几个字段要盯紧:主机名(目标系统的域名,不带 https:// 前缀)、服务号(端口,HTTPS 一般是 443,测试环境可能是别的),还有路径前缀如果对方所有接口都在同一个路径下,可以填在这里,省得每次代码里重复。保存之后,右侧会有一个连接测试按钮,先点一下看能不能通。这个测试非常关键,它走的是系统层面的网络和证书校验,如果这里都通不了,你那几十行 ABAP 代码写得再漂亮也是白搭。通不过常见的原因无非几种:网络不通、端口写错、证书没导入。测试通过后,记住你起的这个目标名,后面代码里 create_by_destination 全靠它。注意:目标一旦上了生产,改动要走传输,所以命名和端口这类信息,建议在测试环境就规划清楚,别等到上线前一天才发现生产端口和测试不一样。2.2 SSL 证书这根刺,绕不过去现在的外部接口基本清一色 HTTPS,证书这件事就成了必答题。SAP 发起 HTTPS 请求时,会去校验对方服务器证书链是否可信。如果对方用的是公有云通用证书,大概率没问题;但如果对方是自签证书,或者用了内部 CA 签发的证书,系统里没有这根根证书,请求就会直接以 SSL 握手失败告终,连 HTTP 状态码都拿不到。处理办法是走 STRUST 事务码,把对方的根证书或中间证书导入到对应的证书列表里,通常是 SSL client SSL Client (Standard) 这一项。导入完成要保存,否则不生效。这里我踩过坑:证书导入后当时有效,过一段时间对方换了证书链,系统又开始报错,一查才发现新证书没导。所以凡是走 HTTPS 的对接,我都建议在监控里加一条,定期看看握手是否还正常,尤其是对方证书临近到期的那几周。2.3 权限与开发包规划权限这块,发起 HTTP 调用本身需要 S_HTTP 或 S_ICF 相关的授权,具体对象取值跟你用的目标、服务有关。很多人调试时报没有权限,第一反应是代码写错了,其实往往是授权对象没配全。稳妥的做法是先把程序写好,在开发或测试环境完整跑一遍,如果报权限错误,用 SU53 看一下缺什么,从那里倒推去申请,比凭空猜快得多。开发包和传输的规划也顺手说一句。我的习惯是,把 HTTP 调用的通用逻辑——比如创建客户端、发请求、解析 JSON、异常处理——封装成一个可复用的工具类,放在一个独立的、稳定的开发包里。真正的业务逻辑放到另一个包里,引用这个工具类。这样将来换认证方式、换对接方,你只动工具类那一处,业务代码基本不用碰。这个分层看着多花十分钟,后面能省下你几个晚上。3. 第一步打通:拿到 token3.1 用 create_by_destination 建立客户端实例真正写代码时,第一步永远是创建客户端实例。前面 SM59 建好的目标,这里就用上了。DATA: lo_client TYPE REF TO if_http_client, lv_http_dest TYPE string. lv_http_dest ZOAUTH_DEMO. SM59 里配置好的目标名 cl_http_clientcreate_by_destination( EXPORTING destination lv_http_dest IMPORTING client lo_client EXCEPTIONS argument_not_found 1 destination_not_found 2 plugin_not_active 3 internal_error 4 OTHERS 5 ).如果目标名写错,create 这一步就会抛 destination_not_found,压根到不了发送环节,所以这类错误一看就明白。创建成功后,lo_client 就是你跟外部世界对话的话筒。这里提醒一句:一个客户端实例用过一次、收到过响应之后,状态就变了,不要拿同一个实例连续发两次不同的请求,该关的关掉、该新建的新建,否则会撞上 http_invalid_state。3.2 拼请求:Header、Body 一个都不能错认证接口通常是个 POST,Content-Type 一般是 application/x-www-form-urlencoded,把客户端标识、客户端密钥、授权类型这些参数拼在请求体里。DATA: lv_body TYPE string. lo_client-request-set_method( POST ). lo_client-request-set_header_field( name Content-Type value application/x-www-form-urlencoded ). lv_body grant_typeclient_credentials client_iddemo_client client_secretdemo_secret. lo_client-request-set_cdata( lv_body ).这里几个细节值得展开。set_cdata 传入的是字符串,如果有中文或特殊字符,要留意编码,尽量用 UTF-8,避免对方收到乱码。Content-Type 必须跟实际载荷格式对上,你发的是表单格式却写 application/json,对方很可能直接 400 拒掉。client_id 和 client_secret 这类敏感信息绝对不能硬编码在代码里,我一般放在加密的自定义表或者安全存储里,程序运行时读出来用。3.3 发送与接收:状态码先看,再谈解析请求拼好,发送和接收是成对出现的。lo_client-send( EXPORTING timeout 30 EXCEPTIONS http_communication_failure 1 http_invalid_state 2 http_processing_failed 3 OTHERS 4 ). lo_client-receive( EXCEPTIONS http_communication_failure 1 http_invalid_state 2 http_processing_failed 3 OTHERS 4 ).发完收完,别急着解析正文,先把状态码抠出来看。DATA: lv_status TYPE i, lv_reason TYPE string. lo_client-response-get_status( IMPORTING code lv_status reason lv_reason ).为什么强调先看状态码?因为很多麻烦都藏在这里。200、201 代表正常;401 是令牌或凭证不对;403 是权限或来源被拒;429 是请求太频繁被限流;5xx 基本是对方服务端出问题。你如果不管状态码直接解析,一旦返回的是个错误页面的 HTML,JSON 解析器会直接 dump,排查起来更费劲。我的习惯是:非 2xx 一律先记日志,把状态码和响应体一起存下来,再决定是重试还是抛错。3.4 解析 JSON 把 token 抠出来对方返回的通常是这样的 JSON:{ access_token: eyJhbGciOi..., token_type: Bearer, expires_in: 3600 }ABAP 里解析 JSON,推荐用标准的 /ui2/cl_json。TYPES: BEGIN OF ty_token_resp, access_token TYPE string, token_type TYPE string, expires_in TYPE i, END OF ty_token_resp. DATA: ls_token TYPE ty_token_resp, lv_resp TYPE string. lv_resp lo_client-response-get_cdata( ). /ui2/cl_jsondeserialize( EXPORTING json lv_resp pretty_name /ui2/cl_jsonpretty_mode-camel_case CHANGING data ls_token ).注意 pretty_name 这个参数。如果对方的字段名是 snake_case(带下划线),而你 ABAP 结构体里写的是 camelCase,就要用对应的 pretty 模式去映射,不然解析出来是空的。这一点我第一次做的时候踩过,字段明明返回了,结构体里就是空值,查了半天才发现是命名映射没对上。解析完,把 access_token 和过期时间一起保存起来,后面业务调用和过期判断都要用。4. 第二步落地:带着 token 调业务接口4.1 Bearer 认证头的正确摆法拿到 token 之后,业务接口的请求头里要加一行 Authorization,值是 Bearer 加令牌本身,中间那个空格千万别漏。DATA: lo_biz TYPE REF TO if_http_client, lv_auth_hdr TYPE string. cl_http_clientcreate_by_destination( EXPORTING destination lv_http_dest IMPORTING client lo_biz ). lo_biz-request-set_method( GET ). lv_auth_hdr |Bearer { ls_token-access_token }|. lo_biz-request-set_header_field( name Authorization value lv_auth_hdr ).这里有个常见误区:有人把令牌直接拼在 URL 的查询参数里,比如 ?tokenxxx。这种做法一是容易被日志记录泄露,二是很多对方的规范现在只认请求头里的 Bearer,参数方式直接 401。所以老老实实放 Authorization 头。4.2 业务报文与响应处理业务接口如果是 GET,参数拼在 URL 后面即可;如果是 POST,按对方要求把 JSON 放进 set_cdata,同时 Content-Type 设成 application/json。发完收完,同样先看状态码,再解析正文。业务响应的结构往往比认证复杂,可能嵌了数组、多层对象。用 /ui2/cl_json 反序列化时,结构体或内表要跟对方字段一一对应。字段一多,手工对很痛苦,我的做法是先拿一份对方的接口文档,把最外层字段先对完,能跑通再往里补,不要一次性全对上,那样出错很难定位。另外,如果响应体很大,记得考虑性能,必要时只取你要的那几个字段。4.3 封装成一个可复用的类前面两步的代码如果散在程序里,下次再对接第二个接口还得重抄一遍。我的做法是封一个类,比如 ZCL_HTTP_TOKEN_CLIENT,对外只暴露两个方法:一个叫 get_token,负责认证并把令牌连同过期时间返回;另一个叫 call_api,负责带上令牌调业务接口并返回正文。类内部把创建客户端、拼头、发请求、异常处理、日志记录全包了。这样做之后,业务程序里就只剩几行:调 get_token 拿令牌,调 call_api 发请求,拿到结果处理数据。对接方的接口地址变了、认证方式改了,你只改这一个类。封装的时候记得把令牌和过期时间用类属性存起来,配合一个时间戳,下次调用前先判断有没有过期,没过期就复用,省掉一次无谓的认证请求。5. 踩坑实录与排查速查5.1 常见错误码对照表排查这类问题,掌握一张错误码速查表能省不少时间。现象/错误可能原因处理方向发送时 dump,提示通信失败网络不通、端口错误、目标未配回 SM59 点连接测试,先排除网络层SSL 握手失败,无状态码证书未导入或已过期用 STRUST 检查并重新导入根证书401 Unauthorized令牌无效、过期或头格式错检查 Bearer 拼写和空格,确认令牌未过期403 Forbidden权限不足或来源受限确认账号是否被授权访问该接口400 Bad Request请求体格式或 Content-Type 不匹配对照文档核验参数名与格式429 Too Many Requests调用频率过高被限流加入退避重试,降低并发解析后字段全为空JSON 命名映射没对上调整 pretty_name 或字段名http_invalid_state复用同一个客户端实例每次请求新建客户端,用完关闭这张表不是让你背,而是当遇到问题时,先照着现象定位是哪一层,再往下钻,比无头苍蝇式地乱改代码高效得多。5.2 几个容易被忽略的细节第一个细节是客户端关闭。每次请求处理完,调用 lo_client-close( ),把连接资源释放掉。长期运行的后台作业如果不关,连接资源会慢慢累积,最后可能影响其他程序。第二个细节是超时设置。send 方法支持传入 timeout,单位是秒。认证接口一般快,30 秒够用;业务接口如果对方要跑复杂查询,可以适当放宽,但也别无限大,不然程序卡死你都不知道卡在哪。第三个细节是日志。把每次请求的状态码、耗时、响应摘要记到自定义日志表里,出问题时不用现场复现就能回溯。日志里绝不能明文记录 client_secret,令牌也要脱敏处理。这个小习惯在跟外部团队扯皮时特别有用,谁的问题一目了然。注意:调试这类接口时,千万别图方便把 client_secret 或完整令牌打印到普通日志里,更不要直接提交到版本管理,泄露一次后患无穷。6. 进阶优化:token 缓存、连接复用与重试6.1 token 缓存,别每次都去换令牌接口一频繁,每次都去认证一次就很浪费。对方系统也可能对认证频率有限制。所以缓存令牌是必须的。最简单的是用类静态属性或内存保留,程序运行期间复用;跨程序的话,可以放到共享内存或自定义表里,存令牌和过期时间戳。判断逻辑很简单:取出令牌,看当前时间戳有没有超过获取时间 expires_in,没超就直接用,超了就重新 get_token。这里建议留一点安全余量,比如 expires_in 是 3600 秒,你实际用到 3300 秒就主动刷新,别卡在最后一秒才换,避免时钟误差或网络延迟导致的边界失效。6.2 连接复用和超时设置HTTP 连接复用是个提效的点。如果 SM59 目标里开启了相关选项,或者代码层面设置了 keep-alive,SAP 在多次请求同一个目标时可以复用底层连接,省掉反复握手的时间。对于调用频繁的接口,这一项带来的延迟下降是能感觉到的。不过要注意,目标层面的设置和代码层面的设置如果冲突,可能出现意想不到的连接异常,所以开之前先在测试环境验证。重试策略也值得一提。网络抖动、对方偶发 5xx,这些都不是你的代码问题,直接抛错让用户重来体验很差。合理的做法是:对可重试的错误(比如超时、429、502、503)做有限次数的退避重试,比如等两秒再试一次,最多三次;对 4xx 这种明确是请求本身有问题的,就别重试了,重试一百次也没用,直接报错反而清晰。最后分享一个我在实际项目里养成的习惯:每对接一个新接口,先不管业务逻辑,单独写一个小程序,只做认证、只调一个最简单的业务接口,把整条链路先跑通,把返回原样打出来看。链路通了,再往里面填真正的业务处理。这个先通链路,再谈业务的顺序,帮我避开了太多一上来就写复杂逻辑、结果不知道错在哪一层的窘境。token 认证这玩意,说穿了就是先换证、再进门,把这两步拆清楚、各自处理好异常,剩下的就是细致功夫了。
返回列表