使用ABAP CL_HTTP_CLIENT类消费OData服务时,如何避免CSRF令牌验证失败错误
Recently I meet with this cookie issue so I document it as a blog in case any other guys might meet with the same problem.
I am trying to create some Opportunity transaction data by consuming OData service via CL_HTTP_CLIENT. Since this is a update operation which needs to be finished by HTTP POST, so a CSRF token is needed in this HTTP post. Let’s first have a look what is a typical scenario running in Chrome extension postman:
(1) Perform a HTTP get operation with header field x-csrf-token = fetch to get a valid CSRF token from http response header field.
(2) launch a HTTP post request using the CSRF token got from previous step,
And a new opportunity could successfully be created in postman:
However, when I implement the above mentioned scenario in ABAP, I meet with trouble. Instead of the expected successful creation message, I always get the error message “CSRF token validation failed”.
Issue trouble shooting
Let’s go back to postman scenario. Although it seems only the header field x-csrf-token is specified in http request, however there is another field cookie which is added to http request under the hood.
We could get this cookie field from previous HTTP get request which is responsible for CSRF token retrieve:
It means in ABAP implementation, we need to first retrieve BOTH CSRF token and cookie field from the first HTTP GET request, and then added these two fields to the request of second HTTP POST request which actually performs the opportunity creation.
This solution is also explained in this thread usage of CSRF token in ABAP report for POST request.
I just added the complete source code of my implementation here and feel free to reuse it. The ABAP report to create Opportunity by consuming OData service:
zcl_odata_tool=>get_csrf_token_and_cookie( IMPORTING et_cookies = DATA(lt_cookie)
ev_token = DATA(lv_token) ).
zcl_odata_tool=>create_opp( iv_token = lv_token it_cookies = lt_cookie ).
Source code for ZCL_ODATA_TOOL:
CLASS zcl_odata_tool DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
CLASS-METHODS get_csrf_token_and_cookie
EXPORTING
!et_cookies TYPE tihttpcki
!ev_token TYPE string .
CLASS-METHODS create_opp
IMPORTING
!iv_token TYPE string
!it_cookies TYPE tihttpcki .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS ZCL_ODATA_TOOL IMPLEMENTATION.
METHOD create_opp.
DEFINE insert_line.
lv_body = lv_body && &1.
lv_body = lv_body && cl_abap_char_utilities=>newline.
END-OF-DEFINITION.
DATA:lo_http_client TYPE REF TO if_http_client,
lv_status TYPE i,
lt_fields TYPE tihttpnvp,
lv_sysubrc TYPE sysubrc.
CALL METHOD cl_http_client=>create_by_url
EXPORTING
url = 'https://<your C4C host>/sap/c4c/odata/v1/c4codata/$batch'
IMPORTING
client = lo_http_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4.
ASSERT sy-subrc = 0.
lo_http_client->propertytype_accept_cookie = if_http_client=>co_enabled.
CALL METHOD lo_http_client->request->set_method( if_http_request=>co_request_method_post ).
lo_http_client->request->set_header_field( name = 'Content-Type' value = 'multipart/mixed; boundary=batch_1' ).
lo_http_client->request->set_header_field( name = 'x-csrf-token' value = iv_token ).
lo_http_client->request->set_header_field( name = 'Authorization' value = 'your basic authentication code' ).
LOOP AT it_cookies ASSIGNING FIELD-SYMBOL(<cookie>).
lo_http_client->request->set_cookie( name = <cookie>-name
value = <cookie>-value ).
ENDLOOP.
DATA: lv_body TYPE string.
insert_line '--batch_1'.
insert_line 'Content-Type: multipart/mixed; boundary=changeset_1'.
lv_body = lv_body && cl_abap_char_utilities=>cr_lf.
*
insert_line '--changeset_1'.
insert_line 'Content-Type: application/http'.
insert_line 'Content-Transfer-Encoding: binary'.
lv_body = lv_body && cl_abap_char_utilities=>cr_lf.
insert_line 'POST OpportunityCollection HTTP/1.1'.
insert_line 'Content-Length: 5000'.
insert_line 'Accept: application/json'.
insert_line 'Content-Type: application/json'.
lv_body = lv_body && cl_abap_char_utilities=>cr_lf.
insert_line '{'.
insert_line '"AccountID": "8000018122",'.
insert_line '"OwnerID": "8000018122",'.
insert_line `"Name": {"content": "Testing ticket creation via OData Jerry1"}`.
insert_line '}'.
insert_line '--changeset_1--'.
lv_body = lv_body && cl_abap_char_utilities=>cr_lf.
insert_line '--batch_1--'.
lo_http_client->request->set_cdata( data = lv_body ).
CALL METHOD lo_http_client->send
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3.
ASSERT sy-subrc = 0.
CALL METHOD lo_http_client->receive
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3.
IF sy-subrc <> 0.
CALL METHOD lo_http_client->get_last_error
IMPORTING
code = lv_sysubrc
message = DATA(ev_message).
WRITE: / 'error occurred during receive data' COLOR COL_NEGATIVE.
RETURN.
ENDIF.
DATA(lv_json) = lo_http_client->response->get_cdata( ).
WRITE:/ lv_json.
ENDMETHOD.
METHOD get_csrf_token_and_cookie.
DATA: lo_http_client TYPE REF TO if_http_client,
lv_status TYPE i,
lt_fields TYPE tihttpnvp,
lv_sysubrc TYPE sysubrc.
CALL METHOD cl_http_client=>create_by_url
EXPORTING
url = 'https://<your C4C host>/sap/c4c/odata/v1/c4codata/'
IMPORTING
client = lo_http_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4.
ASSERT sy-subrc = 0.
lo_http_client->propertytype_accept_cookie = if_http_client=>co_enabled.
CALL METHOD lo_http_client->request->set_method( if_http_request=>co_request_method_get ).
lo_http_client->request->set_header_field( name = 'x-csrf-token' value = 'Fetch' ).
lo_http_client->request->set_header_field( name = 'Accept' value = 'application/json' ).
lo_http_client->request->set_header_field( name = 'Content-Type' value = 'application/json' ).
lo_http_client->request->set_header_field( name = 'Authorization' value = 'Your basic authentication' ).
CALL METHOD lo_http_client->send
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3.
ASSERT sy-subrc = 0.
CALL METHOD lo_http_client->receive
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3.
IF sy-subrc <> 0.
CALL METHOD lo_http_client->get_last_error
IMPORTING
code = lv_sysubrc
message = DATA(ev_message).
WRITE: / 'Error when getting token:', ev_message.
RETURN.
ENDIF.
lo_http_client->response->get_header_fields( CHANGING fields = lt_fields ).
READ TABLE lt_fields ASSIGNING FIELD-SYMBOL(<field>) WITH KEY name = 'x-csrf-token'.
ev_token = <field>-value.
lo_http_client->response->get_cookies( CHANGING cookies = et_cookies ).
lo_http_client->close( ).
ENDMETHOD.
ENDCLASS.
Further reading
You can find a list of all other blogs related to OData written by Jerry.
- Consume standard C4C OData service via ABAP code
- Leverage C4C Odata notification to monitor C4C Opportunity change in CRM system
- OData Service backend implementation in C4C, CRM and S4 HANA
- JMeter beginner – how to use JMeter to measure performance of OData service accessed parallelly
- Regarding cookie manipulation in CL_HTTP_CLIENT to avoid CSRF token validation failure issue
- 剑指OFFER之栈的压入、弹出序列(九度OJ1366)
- Python标准库03 路径与文件 (os.path包, glob包)
- AI人工智能时代已经到来 “北斗即时判”实现纯语音交互
- 剑指OFFER之链表中倒数第k个节点(九度OJ1517)
- 用Qt写软件系列四:定制个性化系统托盘菜单
- Linux简介与厂商版本
- 用Qt写软件系列三:一个简单的系统工具之界面美化
- VS编译链接时错误(Error Link2005)的解决方法
- HttpClient使用心得
- 剑指OFFER之重建二叉树(九度OJ1385)
- 记录visual Studio使用过程中的两个问题
- 剑指OFFER之二维数组中的查找(九度OJ1384)
- Python标准库02 时间与日期 (time, datetime包)
- PR&AE插件开发遇到的一个坑
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 大数据简介,技术体系分类整理
- 图像处理笔记(5)---- OpenCV 用滑动条做调色板
- 牛X | 一款比传统数据库快100-1000倍的数据库,认识一下
- SpringBoot统一参数校验
- SpringBoot多邮件源发送邮件
- 一个基础的SpringBoot项目该包含哪些
- leetcode树之平衡二叉树
- 3分钟短文:说说Laravel页面会话之间的数据保存Session用法
- Skywalking Php注册不上问题排查
- 第4章代码-图形几何变换
- 第5章代码-三维观察
- 我的2020 九月iOS面试秘籍,为你的跳槽保驾护航
- SAP Spartacus layout设计原理
- Angular依赖注入的一个例子和注入原理单步调试
- Angular依赖注入的一个常见错误NullInjectorError,No provider for XXX