对接外围系统多了,也就见识到了各种获取外围系统接口的登录方式。现在总结一下,方便大家找到相似的登录方法进行参考。
这里只涉及一些国产外围系统,认证方式可能不同于一些标准的认证协议,请大家辩证学习。
最简单的登录方式,就是没有登录。一些部署在内网的接口,它们不设置任何安全方式,直接推送信息到地址就完事了。虽然不安全,但对于开发来说,确实省事。常见的产品,大概率是一些中间件缓存产品,比如Redis。数据先存入数据库,后面真正的功能定时读取数据进行处理。
Basic认证,一种比较古老的认证方式。我们需要使用用户名和密码才能登录到对方的系统上。SAP发布的Restful接口也是这种认证方式。对于ABAP开发来说,常用的登录方法有两种:

CALL METHOD lc_http_client->AUTHENTICATEEXPORTINGUSERNAME = ‘’PASSWORD = ‘’.
一种方式是通过类去生成登录信息。
另一种是手动生成认证信息:
CALLMETHOD lc_http_client->request->set_header_fieldEXPORTINGname= 'Authorization'value= ‘’.
这里的Value值,是将用户名+英文冒号+密码的组合,进行base64的编码后的结果。你可以在代码内用函数SSFC_BASE64_ENCODE去转换,也可以在网页找一些开源的工具加密后,直接将结果保存起来使用。
负载中认证,原理是将获取access token的参数封装成一个json之类的东西,负载在HTTP请求的BODY中,然后推送接口,等待服务器返回access token或失败信息。例如CRM公有云平台,纷享开放平台。获取到access token后,后续的业务接口调用,也是将access token融合在接口的负载信息中,比如json中增加一个独立的结构,用来传值企业id,业务id,用户id,access token等,具体的业务数据在另外的一层数据结构中。
通过请求参数认证,首先我们要先清楚什么是请求参数。比如一个SAP发布的服务:
https://XXX:44300/sap/bc/soap/wsdl11?sap-client=100&services=ZFI_INT_001
这里的sap-client和services就是请求参数,参数值分别是100和ZFI_INT_001。能看到,他们的使用方式是在URL后面直接加问号+请求参数和值。多个请求参数通过&符号间隔。比如用友的SRM,就是这种登录方式,当然用友使用了一套更复杂的加密方式,将参数值进行加密后才通过请求参数传递的。这里专门对其进行讲解:
用友申请access token的方法,需要以下参数:appKey、timestamp、signature。
首先我们获取java的时间戳,这里用方法cl_pco_utility=>convert_abap_timestamp_to_java来生成。
我们将appKey和时间戳组合成一个字段,然后进行SHA256的加密。密钥是appselect值。使用cl_abap_hmac=>string_to_xstring将appselect值转成xstring类型的值,appKey和时间戳的组合值也用相同的方式进行格式转换。使用cl_abap_hmac=>calculate_hmac_for_raw方法,得到加密后的值。但是还没有结束,这个值需要用cl_http_utility=>escape_url进行编码,才能得到最终的signature值。
DATA:if_data_s TYPE string,if_data TYPE xstring,if_key TYPE xstring,ef_hmacb64string TYPE string,stamp TYPE timestampl,stamp_char TYPE char22,appkey TYPE ze_appkey,appselect TYPE ze_appselect,timestamp TYPE string,sign TYPE ze_sign.SELECT SINGLE appkey appselect FROM zt3rd_sys_key INTO (appkey,appselect)WHERE sys = 'SRM'.IF sy-dbcnt = 0.MESSAGE 'SRM KEY没有维护' TYPE 'I' RAISING failed.ENDIF.TRY.if_data_s = appselect.if_key = cl_abap_hmac=>string_to_xstring( if_data_s ).CATCH cx_abap_message_digest.MESSAGE '加密异常' TYPE 'I' RAISING failed.ENDTRY.GET TIME STAMP FIELD stamp.stamp_char = stamp.CALL METHOD cl_pco_utility=>convert_abap_timestamp_to_javaEXPORTINGiv_date = CONV #( stamp_char(8) )iv_time = CONV #( stamp_char+8(6) )iv_msec = CONV #( stamp_char+15(3) )IMPORTINGev_timestamp = timestamp.CLEAR if_data_s.if_data_s = |appKey{ appkey }timestamp{ timestamp }|.CONDENSE if_data_s NO-GAPS.TRY.if_data = cl_abap_hmac=>string_to_xstring( if_data_s ).CATCH cx_abap_message_digest.MESSAGE '加密异常' TYPE 'I' RAISING failed.ENDTRY.TRY.CALL METHOD cl_abap_hmac=>calculate_hmac_for_rawEXPORTINGif_algorithm = 'SHA256'if_key = if_keyif_data = if_dataIMPORTINGef_hmacb64string = ef_hmacb64string.CATCH cx_abap_message_digest.MESSAGE '加密异常' TYPE 'I' RAISING failed.ENDTRY.sign = cl_http_utility=>escape_url( ef_hmacb64string ).*调用接口获取tokenDATA: output TYPE string,input TYPE string.DATA: lc_http_client TYPE REF TO if_http_client,l_url TYPE string,l_json_data TYPE string,l_authorization TYPE string.SELECT SINGLE zurl zauthorization FROM zbct_adpdc INTO (l_url,l_authorization)WHERE adpty = 'ZYONBIP_TOKEN'.IF sy-dbcnt = 0.MESSAGE '工具接口没有注册' TYPE 'I' RAISING failed.ENDIF.l_url = |{ l_url }?appKey={ appkey }×tamp={ timestamp }&signature={ sign }|.
接口调用成功后,会得到一个带access_token的json,或者一个失败消息的json返回。
后续的业务接口调用,就简单了,只需要在url后增加?access_token=XXX,就实现了调用。
讲了各种外围系统的接口登录,我在将一种不常见的接口调用方式:Form-data。
同样是Restful的接口,我们用的更多的,是那种往body中放字符串传值接口。比如json、xml,这些本质上就是在进行字符串的传值。但是难免,我们还会遇到传递文件的需求。很显然,这个时候我们就不能用lc_http_client->request->set_cdata来传值。
Form-data特殊的地方在于,每有一行数据,我们必须要给它定义一个独有的类:
DATA: part1 TYPE REF TO if_http_entity.DATA: part2 TYPE REF TO if_http_entity.DATA: part3 TYPE REF TO if_http_entity.……
根据不同的名字,我们set_header_field,独立的设置标签,然后将值append_cdata。
part1 = lc_http_client->request->if_http_entity~add_multipart( ).CALL METHOD part1->set_header_fieldEXPORTINGname = 'Content-Disposition'value = 'form-data; name="companyCode"'.CALL METHOD part1->append_cdataEXPORTINGdata = ls_fields-valueoffset = 0length = lv_len.
当你真的要将文件加入本次传输的时候,首先你需要将文件转成xstring,例如函数:SCMS_BINARY_TO_XSTRING,然后将xstring用set_data或者APPEND_DATA赋值。(可不是CDATA哦,CDATA是字符串)
这里建议参考案例https://www.cnblogs.com/JingYuHai/p/17465211.html
这个例子的局限在于form-data只有一行,如果是多行,需要参照我上方的做法,多次定义form-data行的类才可以。
************************************************************************* 程 序 名:* 程序描述:ABAP调用http接口上传文件* 事务代码:************************************************************************* 修改日志************************************************************************* 日期 版本 修改人 描述* -------- ---- ------------ -------------------------------------------* 20230607 1.0 Amell 创建程序*************************************************************************REPORT zhttp_test MESSAGE-ID 00.************************************************************************* Tables Definitions************************************************************************TABLES: rlgrap.************************************************************************* Data Definitions 定义数据************************************************************************TYPES: BEGIN OF ty_file,line(1024) TYPE x,END OF ty_file.DATA: gt_file TYPE TABLE OF ty_file.DATA: gv_file_name TYPE sdbah-actid,gv_file_type TYPE sdbad-funct,gv_file TYPE xstring.************************************************************************* Includes Module 包含模块************************************************************************************************************************************************* Selection Screen 选择屏幕************************************************************************PARAMETERS: p_file LIKE rlgrap-filename OBLIGATORY.************************************************************************* Initialization 初始化事件************************************************************************INITIALIZATION.************************************************************************* At Selection Screen PAI事件************************************************************************AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.PERFORM frm_f4_file.************************************************************************* At Selection Screen Output PBO事件************************************************************************AT SELECTION-SCREEN OUTPUT.************************************************************************* Report Format 报表格式************************************************************************TOP-OF-PAGE.END-OF-PAGE.************************************************************************* Main Process 主要逻辑************************************************************************START-OF-SELECTION."读取上传文件PERFORM frm_read_upload_file."调用http接口上传文件PERFORM frm_call_http_to_upload_file.END-OF-SELECTION.*&---------------------------------------------------------------------**& Form FRM_F4_FILE*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_f4_file .CALL FUNCTION 'F4_FILENAME'IMPORTINGfile_name = p_file.ENDFORM.*&---------------------------------------------------------------------**& Form FRM_READ_UPLOAD_FILE*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_read_upload_file .DATA: lv_file_path TYPE string,lv_file_length TYPE i,lv_file_name TYPE dbmsgora-filename.lv_file_path = p_file.lv_file_name = p_file.CALL FUNCTION 'SPLIT_FILENAME'EXPORTINGlong_filename = lv_file_name "上传文件路径IMPORTINGpure_filename = gv_file_name "文件名称pure_extension = gv_file_type. "文件后缀CALL FUNCTION 'GUI_UPLOAD'EXPORTINGfilename = lv_file_pathfiletype = 'BIN'IMPORTINGfilelength = lv_file_lengthTABLESdata_tab = gt_fileEXCEPTIONSfile_open_error = 1file_read_error = 2no_batch = 3gui_refuse_filetransfer = 4invalid_type = 5no_authority = 6unknown_error = 7bad_data_format = 8header_not_allowed = 9separator_not_allowed = 10header_too_long = 11unknown_dp_error = 12access_denied = 13dp_out_of_memory = 14disk_full = 15dp_timeout = 16OTHERS = 17.IF sy-subrc <> 0.MESSAGE s001 WITH '上传文件失败' DISPLAY LIKE 'E'.LEAVE LIST-PROCESSING.ENDIF."转xstringCALL FUNCTION 'SCMS_BINARY_TO_XSTRING'EXPORTINGinput_length = lv_file_lengthIMPORTINGbuffer = gv_fileTABLESbinary_tab = gt_fileEXCEPTIONSfailed = 1OTHERS = 2.IF sy-subrc <> 0.MESSAGE s001 WITH '转xstring失败' DISPLAY LIKE 'E'.LEAVE LIST-PROCESSING.ENDIF.ENDFORM.*&---------------------------------------------------------------------**& Form FRM_CALL_HTTP_TO_UPLOAD_FILE*&---------------------------------------------------------------------**& text*&---------------------------------------------------------------------**& --> p1 text*& <-- p2 text*&---------------------------------------------------------------------*FORM frm_call_http_to_upload_file .DATA: lo_http_client TYPE REF TO if_http_client, "http客户端lo_http_entity TYPE REF TO if_http_entity, "http实体lt_http_header TYPE tihttpnvp, "http头部信息ls_http_header TYPE ihttpnvp,lv_url TYPE string, "http请求lv_len TYPE i, "请求数据的长度lv_response TYPE string, "http响应返回信息lv_code TYPE i. "http状态码DATA: lv_error_code TYPE sysubrc,lv_error_msg TYPE string.DATA: lv_file_name TYPE savwctxt-fieldcont,lv_name_encode TYPE savwctxt-fieldcont.DATA: lv_content_disposition TYPE string,lv_content_type TYPE string.lv_url = 'http://192.168.194.12:8080/jeecg-boot/ncip/file/upload'."创建http客户端请求CALL METHOD cl_http_client=>create_by_urlEXPORTINGurl = lv_urlIMPORTINGclient = lo_http_clientEXCEPTIONSargument_not_found = 1plugin_not_active = 2internal_error = 3OTHERS = 4."设置http为post请求lo_http_client->request->set_method( 'POST' ).ls_http_header-name = 'X-Access-Token'.ls_http_header-value = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2ODYxODM1OTcsInVzZXJuYW1lIjoiYWRtaW4ifQ.fpxtvBsFDVR5WmjP3iLXhD-K7ZiULofqwQ1S_DE9Hxk'.APPEND ls_http_header TO lt_http_header.ls_http_header-name = 'Content-Type'.ls_http_header-value = 'multipart/form-data'.APPEND ls_http_header TO lt_http_header."设置http headerCALL METHOD lo_http_client->request->set_header_fieldsEXPORTINGfields = lt_http_header.* CALL METHOD lo_http_client->request->if_http_entity~set_formfield_encoding* EXPORTING* formfield_encoding = cl_http_request=>if_http_entity~co_encoding_raw.lo_http_entity = lo_http_client->request->if_http_entity~add_multipart( )."utf-8编码文件名(目的是让上传后的文件名跟原来一样,不然会乱码)lv_file_name = gv_file_name.CALL FUNCTION 'WWW_URLENCODE'EXPORTINGvalue = lv_file_nameIMPORTINGvalue_encoded = lv_name_encode.lv_content_disposition = 'form-data; name="file"; filename="' && lv_name_encode && '.' && gv_file_type && '"'.CALL METHOD lo_http_entity->set_header_fieldEXPORTINGname = 'Content-Disposition'"value = 'form-data; name="file"; filename="测试文件.pdf"'value = lv_content_disposition."文件后缀转小写TRANSLATE gv_file_type TO LOWER CASE.CASE gv_file_type.WHEN 'jpg'.lv_content_type = 'image/jpeg'.WHEN 'png'.lv_content_type = 'image/png'.WHEN 'txt'.lv_content_type = 'text/plain'.WHEN 'pdf'.lv_content_type = 'application/pdf'.WHEN 'xlsx'.lv_content_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'.WHEN 'xls'.lv_content_type = 'application/vnd.ms-excel'.WHEN 'docx'.lv_content_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'.WHEN 'doc'.lv_content_type = 'application/msword'.ENDCASE.CALL METHOD lo_http_entity->set_content_typeEXPORTINGcontent_type = lv_content_type.lv_len = xstrlen( gv_file ).CALL METHOD lo_http_entity->set_dataEXPORTINGdata = gv_fileoffset = 0length = lv_len."发送请求CALL METHOD lo_http_client->sendEXCEPTIONShttp_communication_failure = 1http_invalid_state = 2http_processing_failed = 3http_invalid_timeout = 4OTHERS = 5.IF sy-subrc NE 0.CALL METHOD lo_http_client->get_last_errorIMPORTINGcode = lv_error_codemessage = lv_error_msg.ENDIF."请求返回结果CALL METHOD lo_http_client->receiveEXCEPTIONShttp_communication_failure = 1http_invalid_state = 2http_processing_failed = 3."请求返回的状态码CALL METHOD lo_http_client->response->get_statusIMPORTINGcode = lv_code."获取接口返回的数据lv_response = lo_http_client->response->get_cdata( ).cl_demo_output=>write( lv_response ).cl_demo_output=>display( ).ENDFORM.

