├── .abapgit.xml
├── .gitignore
├── LICENSE
├── README.md
├── abaplint.json
├── doc
└── jwt_profile.png
└── src
├── package.devc.xml
├── zcl_jwt_generator.clas.abap
├── zcl_jwt_generator.clas.testclasses.abap
├── zcl_jwt_generator.clas.xml
├── zcx_jwt_generator.clas.abap
├── zcx_jwt_generator.clas.xml
└── zjwt_profile.tabl.xml
/.abapgit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | E
6 | /src/
7 | PREFIX
8 |
9 | - /.gitignore
10 | - /LICENSE
11 | - /README.md
12 | - /package.json
13 | - /.travis.yml
14 | - /.gitlab-ci.yml
15 | - /abaplint.json
16 | - /azure-pipelines.yml
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | src/zcl_jwt_generator.clas.abap.bak
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 abapChaoLiu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # ABAP JWT Generator
4 | Generate Json Web Token (JWT) in ABAP.
5 |
6 | ## Demo 1 - method get_jwt_by_profile().
7 |
8 | If JWT profile is maintained in table ZJWT_PROFILE, use method get_jwt_by_profile() to derive JWT token by profile name.
9 |
10 | 
11 |
12 | ```abap
13 | DATA: jwt_generator TYPE REF TO zcl_jwt_generator,
14 | jwt TYPE string.
15 |
16 | CREATE OBJECT jwt_client.
17 | jwt = jwt_client->get_jwt_by_profile( 'JWT_PROFILE_NAME' ).
18 | ```
19 |
20 | ## Demo 2 - method generate_jwt().
21 | If JWT profile is not maintained, use method generate_jwt() to derive JWT token.
22 |
23 | ```abap
24 | REPORT zrpt_jwt_generator_demo.
25 |
26 | DATA: jwt_generator TYPE REF TO zcl_jwt_generator.
27 | DATA: jwt_header TYPE zcl_jwt_generator=>ty_jwt_header,
28 | jwt_claim TYPE zcl_jwt_generator=>ty_jwt_claim.
29 | DATA: exp_second TYPE int8,
30 | ssf_info TYPE ssfinfo.
31 | DATA: start_timestamp TYPE timestamp VALUE '19700101000000',
32 | ssf_id TYPE ssfid VALUE '',
33 | "PSE profile with private key
34 | ssf_profile TYPE ssfprof VALUE 'SAPZJWTSF001.pse'.
35 |
36 |
37 | jwt_header-alg = 'RS256'.
38 | jwt_claim = VALUE #( iss = 'UserID'
39 | sub = 'example@gmail.com'
40 | aud = 'https://login.example.com'
41 | exp = exp_second ).
42 |
43 | ssf_info = VALUE #( id = ssf_id profile = ssf_profile ).
44 |
45 | CREATE OBJECT jwt_generator.
46 | TRY.
47 | jwt_generator->generate_jwt(
48 | EXPORTING
49 | jwt_header = jwt_header
50 | jwt_claim = jwt_claim
51 | ssf_info = ssfinfo
52 | RECEIVING
53 | jwt = DATA(jwt) ).
54 | CATCH zcx_jwt_generator INTO DATA(lo_exp).
55 | WRITE /: 'ERROR when generate JWT token.'.
56 | WRITE /: lo_exp->get_text( ).
57 | ENDTRY.
58 | ```
59 |
60 | ## Demo 3 - method get_access_token_by_profile().
61 |
62 | If JWT profile is maintained in table ZJWT_PROFILE, use method get_access_token_by_profile() to get JWT Access Token.
63 |
64 | 
65 |
66 | ```abap
67 | DATA: jwt_generator TYPE REF TO zcl_jwt_generator,
68 | jwt_access_token TYPE string.
69 |
70 | CREATE OBJECT jwt_client.
71 | jwt_access_token = jwt_client->get_access_token_by_profile( 'JWT_PROFILE_NAME' ).
72 | ```
73 |
74 | ## Demo 4 - method generate_jwt_with_secret().
75 | You can use method generate_jwt_with_secret() to generate a JWT with the secret key, like [JWT Debugger](https://jwt.io/#debugger-io).
76 | ```abap
77 | GET TIME STAMP FIELD DATA(timestamp).
78 |
79 | DATA(jwt_generator) = NEW zcl_jwt_generator( ).
80 |
81 | TRY.
82 | DATA(jwt_result) =
83 | jwt_generator->generate_jwt_with_secret(
84 | jwt_header = VALUE #( alg = 'HS256' typ = 'JWT' )
85 | jwt_claim = VALUE #( sub = '1234567890'
86 | name = 'John Doe'
87 | iat = jwt_generator->convert_abap_timestamp_to_unix( timestamp ) )
88 | secret = '1WRAv0Usf90-jr2W7UQBQBLvIBBFq8vumq-VzrR3h7E'
89 | algorithm = 'SHA256' ).
90 | CATCH zcx_jwt_generator INTO DATA(lx_jwt).
91 | ENDTRY.
92 | ```
93 |
94 | ## Credits and references
95 |
96 | Class ZCL_JWT_Generator is modified from Dimitri Seifmann's post [Connect from AS ABAP to Google Cloud Platform App-Engine resource secured with Google Identity-Aware Proxy](https://blogs.sap.com/2019/11/10/connect-from-as-abap-to-google-cloud-platform-app-engine-resource-secured-with-google-identity-aware-proxy/).
97 |
--------------------------------------------------------------------------------
/abaplint.json:
--------------------------------------------------------------------------------
1 | {
2 | "global": {
3 | "files": "/src/**/*.*",
4 | "skipGeneratedGatewayClasses": true,
5 | "skipGeneratedPersistentClasses": true,
6 | "skipGeneratedFunctionGroups": true
7 | },
8 | "dependencies": [
9 | {
10 | "url": "https://github.com/abaplint/deps",
11 | "folder": "/deps",
12 | "files": "/src/**/*.*"
13 | }
14 | ],
15 | "syntax": {
16 | "version": "v740sp08",
17 | "errorNamespace": "^(Z|Y)",
18 | "globalConstants": [
19 | "abap_func_exporting",
20 | "abap_func_tables",
21 | "cssf_formtype_text",
22 | "seoc_category_exception",
23 | "seoc_category_webdynpro_class",
24 | "seoc_exposure_private",
25 | "seoc_exposure_protected",
26 | "seoc_exposure_public",
27 | "seoc_version_active",
28 | "seoc_version_inactive",
29 | "seok_access_free",
30 | "seok_access_modify",
31 | "seok_pgmid_r3tr",
32 | "seop_ext_class_locals_def",
33 | "seop_ext_class_locals_imp",
34 | "seop_ext_class_macros",
35 | "seop_ext_class_testclasses",
36 | "sews_c_vif_version",
37 | "skwfc_obtype_folder",
38 | "skwfc_obtype_loio",
39 | "so2_controller",
40 | "ststc_c_type_dialog",
41 | "ststc_c_type_object",
42 | "ststc_c_type_parameters",
43 | "ststc_c_type_report",
44 | "swbm_c_op_delete_no_dialog",
45 | "swbm_c_type_ddic_db_tabxinx",
46 | "swbm_c_type_wdy_application",
47 | "swbm_version_active",
48 | "swbm_version_inactive",
49 | "wbmr_c_skwf_folder_class",
50 | "wdyn_limu_component_controller",
51 | "wdyn_limu_component_definition",
52 | "wdyn_limu_component_view"
53 | ],
54 | "globalMacros": []
55 | },
56 | "rules": {
57 | "prefix_is_current_class": {
58 | "omitMeInstanceCalls": false
59 | },
60 | "allowed_object_naming": true,
61 | "check_comments": false,
62 | "fully_type_constants": true,
63 | "keep_single_parameter_on_one_line": false,
64 | "prefer_returning_to_exporting": false,
65 | "selection_screen_naming": true,
66 | "sicf_consistency": true,
67 | "sql_escape_host_variables": false,
68 | "xml_consistency": true,
69 | "check_no_handler_pragma": true,
70 | "newline_between_methods": false,
71 | "chain_mainly_declarations": true,
72 | "check_abstract": true,
73 | "check_text_elements": true,
74 | "type_begin_single_include": true,
75 | "types_naming": false,
76 | "7bit_ascii": {
77 | "exclude": ["zcl_abapgit_utils.clas.testclasses.abap"]
78 | },
79 | "abapdoc": false,
80 | "check_ddic": true,
81 | "check_include": true,
82 | "form_no_dash": false,
83 | "allowed_object_types": {
84 | "allowed": [
85 | "PROG",
86 | "CLAS",
87 | "INTF",
88 | "FUGR",
89 | "DEVC",
90 | "TRAN",
91 | "W3MI",
92 | "TABL"
93 | ]
94 | },
95 | "ambiguous_statement": true,
96 | "avoid_use": {
97 | "define": false,
98 | "endselect": true,
99 | "execSQL": true,
100 | "kernelCall": true,
101 | "communication": true,
102 | "systemCall": true,
103 | "defaultKey": false,
104 | "break": true,
105 | "statics": true
106 | },
107 | "begin_end_names": true,
108 | "check_transformation_exists": true,
109 | "check_syntax": false,
110 | "class_attribute_names": {
111 | "ignoreExceptions": true,
112 | "statics": ".",
113 | "instance": "."
114 | },
115 | "cloud_types": true,
116 | "colon_missing_space": true,
117 | "commented_code": false,
118 | "constructor_visibility_public": true,
119 | "contains_tab": true,
120 | "definitions_top": true,
121 | "description_empty": true,
122 | "double_space": false,
123 | "empty_line_in_statement": {
124 | "allowChained": true
125 | },
126 | "empty_statement": true,
127 | "empty_structure": {
128 | "loop": true,
129 | "if": false,
130 | "while": true,
131 | "case": true,
132 | "select": true,
133 | "do": true,
134 | "at": true
135 | },
136 | "exit_or_check": true,
137 | "exporting": true,
138 | "form_tables_obsolete": false,
139 | "functional_writing": {
140 | "ignoreExceptions": true
141 | },
142 | "global_class": true,
143 | "identical_form_names": true,
144 | "if_in_if": true,
145 | "implement_methods": false,
146 | "in_statement_indentation": false,
147 | "indentation": {
148 | "ignoreExceptions": true,
149 | "alignTryCatch": false,
150 | "globalClassSkipFirst": false,
151 | "ignoreGlobalClassDefinition": false,
152 | "ignoreGlobalInterface": false
153 | },
154 | "inline_data_old_versions": true,
155 | "line_length": {
156 | "length": 240
157 | },
158 | "line_only_punc": {
159 | "ignoreExceptions": true
160 | },
161 | "local_class_naming": {
162 | "exception": ".",
163 | "local": ".",
164 | "test": "."
165 | },
166 | "local_testclass_location": true,
167 | "local_variable_names": {
168 | "expectedData": ".",
169 | "expectedConstant": ".",
170 | "expectedFS": "."
171 | },
172 | "main_file_contents": true,
173 | "max_one_statement": true,
174 | "message_exists": false,
175 | "method_length": {
176 | "statements": 100,
177 | "ignoreTestClasses": false,
178 | "errorWhenEmpty": false
179 | },
180 | "method_parameter_names": {
181 | "ignoreExceptions": true,
182 | "importing": ".",
183 | "returning": ".",
184 | "changing": ".",
185 | "exporting": ".",
186 | "ignoreNames": []
187 | },
188 | "mix_returning": true,
189 | "msag_consistency": true,
190 | "nesting": {
191 | "depth": 5
192 | },
193 | "no_public_attributes": false,
194 | "object_naming": {
195 | "clas": "^ZC(L|X)\\_",
196 | "intf": "^ZIF\\_",
197 | "prog": "^Z",
198 | "fugr": "^Z",
199 | "tabl": "^Z",
200 | "ttyp": "^Z",
201 | "dtel": "^Z",
202 | "doma": "^Z",
203 | "msag": "^Z",
204 | "tran": "^Z",
205 | "enqu": "^EZ",
206 | "auth": "^Z",
207 | "pinf": "^Z",
208 | "idoc": "^Z",
209 | "xslt": "^Z"
210 | },
211 | "obsolete_statement": {
212 | "refresh": true,
213 | "compute": true,
214 | "add": true,
215 | "subtract": true,
216 | "multiply": true,
217 | "move": true,
218 | "divide": true,
219 | "requested": true,
220 | "occurs": true
221 | },
222 | "parser_error": {},
223 | "preferred_compare_operator": {
224 | "badOperators": [
225 | "EQ",
226 | "><",
227 | "NE",
228 | "GE",
229 | "GT",
230 | "LT",
231 | "LE"
232 | ]
233 | },
234 | "release_idoc": true,
235 | "remove_descriptions": {
236 | "ignoreExceptions": true
237 | },
238 | "rfc_error_handling": false,
239 | "sequential_blank": {
240 | "lines": 4
241 | },
242 | "short_case": {
243 | "length": 0,
244 | "allow": [
245 | "iv_action",
246 | "sy"
247 | ]
248 | },
249 | "space_before_colon": true,
250 | "space_before_dot": {
251 | "ignoreGlobalDefinition": true,
252 | "ignoreExceptions": true
253 | },
254 | "start_at_tab": true,
255 | "superclass_final": true,
256 | "tabl_enhancement_category": true,
257 | "type_form_parameters": false,
258 | "unreachable_code": true,
259 | "use_new": true,
260 | "when_others_last": true,
261 | "whitespace_end": false
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/doc/jwt_profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abapChaoLiu/abap_jwt_generator/ac8abf9ffdd62c90ebfa5fb9cfe33995c028d6c7/doc/jwt_profile.png
--------------------------------------------------------------------------------
/src/package.devc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | JWT Generator
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/zcl_jwt_generator.clas.abap:
--------------------------------------------------------------------------------
1 | CLASS zcl_jwt_generator DEFINITION
2 | PUBLIC
3 | CREATE PUBLIC .
4 |
5 | PUBLIC SECTION.
6 | TYPES:
7 | BEGIN OF ty_jwt_header,
8 | alg TYPE string,
9 | typ TYPE string,
10 | END OF ty_jwt_header .
11 | TYPES:
12 | BEGIN OF ty_jwt_claim,
13 | name TYPE string, " Name
14 | iss TYPE string, "Issuer
15 | sub TYPE string, "Subject
16 | aud TYPE string, "Audience
17 | exp TYPE string, "Expiration Time
18 | nbf TYPE string, "Not Before
19 | iat TYPE string, "Issued At
20 | jti TYPE string, "JWT ID
21 | END OF ty_jwt_claim .
22 |
23 | METHODS generate_jwt
24 | IMPORTING
25 | !jwt_header TYPE ty_jwt_header
26 | !jwt_claim TYPE ty_jwt_claim
27 | !ssf_info TYPE ssfinfo
28 | !ssf_format TYPE ssfform DEFAULT 'PKCS1-V1.5'
29 | !ssf_hash_agrithm TYPE ssfhash DEFAULT 'SHA256'
30 | RETURNING
31 | VALUE(jwt) TYPE string
32 | RAISING
33 | zcx_jwt_generator .
34 |
35 | METHODS generate_jwt_with_secret
36 | IMPORTING
37 | !jwt_header TYPE ty_jwt_header
38 | !jwt_claim TYPE ty_jwt_claim
39 | !secret TYPE string
40 | !algorithm TYPE string DEFAULT 'SHA256'
41 | RETURNING
42 | VALUE(result) TYPE string
43 | RAISING
44 | zcx_jwt_generator .
45 |
46 | METHODS get_jwt_by_profile
47 | IMPORTING
48 | !profile TYPE zjwt_profile-profile_name
49 | RETURNING
50 | VALUE(jwt) TYPE string
51 | RAISING
52 | zcx_jwt_generator .
53 |
54 | METHODS get_access_token_by_profile
55 | IMPORTING
56 | !profile TYPE zjwt_profile-profile_name
57 | RETURNING
58 | VALUE(access_token) TYPE string
59 | RAISING
60 | zcx_jwt_generator .
61 |
62 | METHODS base64url_encode
63 | IMPORTING
64 | !unencoded TYPE string
65 | RETURNING
66 | VALUE(base64url) TYPE string .
67 |
68 | METHODS convert_abap_timestamp_to_unix
69 | IMPORTING
70 | !timestamp TYPE timestamp
71 | RETURNING
72 | VALUE(result) TYPE i .
73 |
74 | PROTECTED SECTION.
75 |
76 | PRIVATE SECTION.
77 | TYPES:
78 | ty_tssfbin TYPE STANDARD TABLE OF ssfbin WITH KEY table_line WITHOUT FURTHER SECONDARY KEYS.
79 |
80 | DATA:
81 | jwt_profile TYPE zjwt_profile.
82 |
83 | METHODS format_jwt_claim
84 | IMPORTING jwt_claim TYPE ty_jwt_claim
85 | RETURNING VALUE(result) TYPE ty_jwt_claim.
86 |
87 | METHODS string_to_binary_tab
88 | IMPORTING input_string TYPE string
89 | RETURNING VALUE(output_bins) TYPE ty_tssfbin
90 | RAISING zcx_jwt_generator.
91 |
92 | METHODS binary_tab_to_string
93 | IMPORTING input_bins TYPE ty_tssfbin
94 | length TYPE ssflen
95 | RETURNING VALUE(output_string) TYPE string
96 | RAISING zcx_jwt_generator.
97 |
98 | METHODS format_base64
99 | IMPORTING input TYPE string
100 | RETURNING VALUE(result) TYPE string.
101 |
102 | ENDCLASS.
103 |
104 |
105 | CLASS zcl_jwt_generator IMPLEMENTATION.
106 |
107 |
108 | METHOD base64url_encode.
109 | base64url = format_base64( cl_http_utility=>encode_base64( unencoded = unencoded ) ).
110 | ENDMETHOD.
111 |
112 |
113 | METHOD binary_tab_to_string.
114 | CALL FUNCTION 'SCMS_BINARY_TO_STRING'
115 | EXPORTING
116 | input_length = length
117 | encoding = '4110'
118 | IMPORTING
119 | text_buffer = output_string
120 | TABLES
121 | binary_tab = input_bins
122 | EXCEPTIONS
123 | failed = 1
124 | OTHERS = 2.
125 | IF sy-subrc <> 0.
126 | zcx_jwt_generator=>raise_system( ).
127 | ENDIF.
128 | ENDMETHOD.
129 |
130 |
131 | METHOD convert_abap_timestamp_to_unix.
132 |
133 | CONVERT TIME STAMP timestamp TIME ZONE 'UTC' INTO DATE DATA(date) TIME DATA(time).
134 |
135 | cl_pco_utility=>convert_abap_timestamp_to_java(
136 | EXPORTING
137 | iv_date = date
138 | iv_time = time
139 | IMPORTING
140 | ev_timestamp = DATA(lv_unix_timestamp) ).
141 |
142 | result =
143 | COND #(
144 | WHEN strlen( lv_unix_timestamp ) > 10
145 | THEN lv_unix_timestamp(10)
146 | ELSE lv_unix_timestamp ).
147 |
148 | ENDMETHOD.
149 |
150 |
151 | METHOD format_base64.
152 |
153 | result = input.
154 |
155 | REPLACE ALL OCCURRENCES OF '=' IN result WITH ''.
156 | REPLACE ALL OCCURRENCES OF '+' IN result WITH '-'.
157 | REPLACE ALL OCCURRENCES OF '/' IN result WITH '_'.
158 |
159 | ENDMETHOD.
160 |
161 |
162 | METHOD generate_jwt.
163 |
164 | DATA input_bins TYPE STANDARD TABLE OF ssfbin.
165 | DATA output_bins TYPE STANDARD TABLE OF ssfbin.
166 | DATA input_length TYPE ssflen.
167 | DATA output_length TYPE ssflen.
168 | DATA output_crc TYPE ssfreturn.
169 | DATA signers TYPE STANDARD TABLE OF ssfinfo.
170 | DATA: jwt_claim_json TYPE string,
171 | jwt_header_json TYPE string,
172 | jwt_header_base64url TYPE string,
173 | jwt_claim_base64url TYPE string.
174 | DATA input_base64url TYPE string.
175 | DATA: signature TYPE string,
176 | signature_base64url TYPE string.
177 |
178 |
179 | jwt_header_json = /ui2/cl_json=>serialize(
180 | compress = abap_true
181 | data = jwt_header
182 | pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
183 |
184 | jwt_claim_json = /ui2/cl_json=>serialize(
185 | compress = abap_true
186 | data = jwt_claim
187 | pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
188 |
189 |
190 | jwt_header_base64url = base64url_encode( jwt_header_json ).
191 | jwt_claim_base64url = base64url_encode( jwt_claim_json ).
192 |
193 |
194 | input_base64url = |{ jwt_header_base64url }.{ jwt_claim_base64url }|.
195 | input_length = strlen( input_base64url ).
196 |
197 | input_bins = string_to_binary_tab( input_string = input_base64url ).
198 |
199 |
200 | APPEND ssf_info TO signers.
201 |
202 | CALL FUNCTION 'SSF_KRN_SIGN'
203 | EXPORTING
204 | str_format = ssf_format
205 | b_inc_certs = abap_false
206 | b_detached = abap_false
207 | b_inenc = abap_false
208 | ostr_input_data_l = input_length
209 | str_hashalg = ssf_hash_agrithm
210 | IMPORTING
211 | ostr_signed_data_l = output_length
212 | crc = output_crc " SSF Return code
213 | TABLES
214 | ostr_input_data = input_bins
215 | signer = signers
216 | ostr_signed_data = output_bins
217 | EXCEPTIONS
218 | ssf_krn_error = 1
219 | ssf_krn_noop = 2
220 | ssf_krn_nomemory = 3
221 | ssf_krn_opinv = 4
222 | ssf_krn_nossflib = 5
223 | ssf_krn_signer_list_error = 6
224 | ssf_krn_input_data_error = 7
225 | ssf_krn_invalid_par = 8
226 | ssf_krn_invalid_parlen = 9
227 | ssf_fb_input_parameter_error = 10.
228 | IF sy-subrc <> 0.
229 | zcx_jwt_generator=>raise_system( ).
230 | ENDIF.
231 |
232 | signature = binary_tab_to_string( input_bins = output_bins
233 | length = output_length ).
234 |
235 | signature_base64url = base64url_encode( signature ).
236 |
237 | jwt = |{ input_base64url }.{ signature_base64url }|.
238 |
239 | ENDMETHOD.
240 |
241 |
242 | METHOD generate_jwt_with_secret.
243 |
244 | DATA(header_json) =
245 | /ui2/cl_json=>serialize(
246 | data = jwt_header
247 | compress = abap_true
248 | pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
249 |
250 | DATA(header_base64) = base64url_encode( header_json ).
251 |
252 | DATA(payload_json) =
253 | /ui2/cl_json=>serialize(
254 | data = format_jwt_claim( jwt_claim )
255 | compress = abap_true
256 | pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
257 |
258 | DATA(payload_base64) = base64url_encode( payload_json ).
259 |
260 | DATA(data_to_sign) =
261 | |{ header_base64 }.{ payload_base64 }|.
262 |
263 | TRY.
264 | cl_abap_hmac=>calculate_hmac_for_char(
265 | EXPORTING
266 | if_algorithm = algorithm
267 | if_key = cl_abap_hmac=>string_to_xstring( secret )
268 | if_data = data_to_sign
269 | IMPORTING
270 | ef_hmacb64string = DATA(hmacb64string) ).
271 | CATCH cx_abap_message_digest INTO DATA(lx_message_digest).
272 | RAISE EXCEPTION TYPE zcx_jwt_generator
273 | EXPORTING
274 | previous = lx_message_digest.
275 | ENDTRY.
276 |
277 | DATA(lv_signature) = format_base64( hmacb64string ).
278 |
279 | result =
280 | |{ header_base64 }.{ payload_base64 }.{ lv_signature }|.
281 |
282 | ENDMETHOD.
283 |
284 |
285 | METHOD get_access_token_by_profile.
286 |
287 | DATA:
288 | BEGIN OF ls_token,
289 | access_token TYPE string,
290 | END OF ls_token.
291 | DATA:
292 | lo_client TYPE REF TO if_http_client,
293 | jwt TYPE string.
294 | FIELD-SYMBOLS:
295 | TYPE data,
296 | TYPE data.
297 |
298 | jwt = get_jwt_by_profile( profile = profile ).
299 |
300 | cl_http_client=>create_by_url(
301 | EXPORTING
302 | url = jwt_profile-aud " URL
303 | ssl_id = 'ANONYM' " SSL Identity
304 | IMPORTING
305 | client = lo_client " HTTP Client Abstraction
306 | EXCEPTIONS
307 | argument_not_found = 1
308 | plugin_not_active = 2
309 | internal_error = 3
310 | OTHERS = 4 ).
311 | IF sy-subrc <> 0.
312 | zcx_jwt_generator=>raise_system( ).
313 | ENDIF.
314 |
315 |
316 | IF lo_client IS NOT BOUND.
317 | zcx_jwt_generator=>raise_system( ).
318 | ENDIF.
319 |
320 | lo_client->request->set_method( if_http_request=>co_request_method_post ).
321 | lo_client->request->set_formfield_encoding( formfield_encoding = if_http_entity=>co_formfield_encoding_encoded ).
322 |
323 | lo_client->request->set_header_field(
324 | name = 'Content-type' " Name of the header field
325 | value = 'application/x-www-form-urlencoded' ). " HTTP header field value
326 |
327 | lo_client->request->set_form_field(
328 | name = 'grant_type'
329 | value = 'urn:ietf:params:oauth:grant-type:jwt-bearer' ).
330 |
331 | lo_client->request->set_form_field(
332 | name = 'assertion'
333 | value = jwt ).
334 |
335 | lo_client->send( ).
336 | lo_client->receive(
337 | EXCEPTIONS
338 | http_communication_failure = 1
339 | http_invalid_state = 2
340 | http_processing_failed = 3 ).
341 | IF sy-subrc <> 0.
342 | zcx_jwt_generator=>raise_system( ).
343 | ENDIF.
344 |
345 | DATA(response_json) = lo_client->response->get_cdata( ).
346 |
347 | lo_client->close( ).
348 | IF sy-subrc <> 0.
349 | zcx_jwt_generator=>raise_system( ).
350 | ENDIF.
351 |
352 | /ui2/cl_json=>deserialize(
353 | EXPORTING
354 | json = response_json
355 | CHANGING
356 | data = ls_token ).
357 |
358 | access_token = ls_token-access_token.
359 |
360 | ENDMETHOD.
361 |
362 |
363 | METHOD get_jwt_by_profile.
364 |
365 | DATA: jwt_header TYPE ty_jwt_header,
366 | jwt_claim TYPE ty_jwt_claim.
367 |
368 | DATA: current_timestamp TYPE timestamp,
369 | exp_timestamp TYPE tzntstmpl,
370 | diff_second TYPE tzntstmpl,
371 | exp_second TYPE int8,
372 | ssfinfo TYPE ssfinfo.
373 | CONSTANTS: start_timestamp TYPE timestamp VALUE '19700101000000'.
374 |
375 | SELECT SINGLE * FROM zjwt_profile INTO jwt_profile
376 | WHERE profile_name = profile.
377 | IF sy-subrc = 0.
378 | GET TIME STAMP FIELD current_timestamp.
379 | cl_abap_tstmp=>add(
380 | EXPORTING
381 | tstmp = current_timestamp " UTC Time Stamp
382 | secs = jwt_profile-time_interval " Time Interval in Seconds
383 | RECEIVING
384 | r_tstmp = exp_timestamp ). " UTC Time Stamp
385 | cl_abap_tstmp=>subtract(
386 | EXPORTING
387 | tstmp1 = exp_timestamp " UTC Time Stamp
388 | tstmp2 = start_timestamp " UTC Time Stamp
389 | RECEIVING
390 | r_secs = diff_second ). " Time Interval in Seconds
391 |
392 |
393 | MOVE-CORRESPONDING jwt_profile TO jwt_claim.
394 | exp_second = diff_second.
395 | jwt_claim-exp = exp_second.
396 |
397 | ssfinfo-id = jwt_profile-ssf_id.
398 | ssfinfo-profile = jwt_profile-ssf_profile.
399 | jwt_header-alg = jwt_profile-alg.
400 |
401 | generate_jwt(
402 | EXPORTING
403 | jwt_header = jwt_header
404 | jwt_claim = jwt_claim
405 | ssf_info = ssfinfo
406 | RECEIVING
407 | jwt = jwt ).
408 |
409 | ELSE.
410 | "TO DO
411 | zcx_jwt_generator=>raise_system( ).
412 | ENDIF.
413 |
414 | ENDMETHOD.
415 |
416 |
417 | METHOD format_jwt_claim.
418 |
419 | result = jwt_claim.
420 |
421 | result-iat = condense( result-iat ).
422 | result-exp = condense( result-exp ).
423 |
424 | ENDMETHOD.
425 |
426 |
427 | METHOD string_to_binary_tab.
428 | DATA lv_xstring TYPE xstring.
429 |
430 | CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
431 | EXPORTING
432 | text = input_string
433 | encoding = '4110'
434 | IMPORTING
435 | buffer = lv_xstring
436 | EXCEPTIONS
437 | failed = 1
438 | OTHERS = 2.
439 | IF sy-subrc <> 0.
440 | zcx_jwt_generator=>raise_system( ).
441 | ENDIF.
442 |
443 | CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
444 | EXPORTING
445 | buffer = lv_xstring
446 | TABLES
447 | binary_tab = output_bins.
448 | IF sy-subrc <> 0.
449 | zcx_jwt_generator=>raise_system( ).
450 | ENDIF.
451 | ENDMETHOD.
452 | ENDCLASS.
453 |
--------------------------------------------------------------------------------
/src/zcl_jwt_generator.clas.testclasses.abap:
--------------------------------------------------------------------------------
1 | CLASS ltcl_test DEFINITION FINAL
2 | FOR TESTING
3 | RISK LEVEL HARMLESS
4 | DURATION SHORT.
5 |
6 | PUBLIC SECTION.
7 |
8 | PRIVATE SECTION.
9 | METHODS generate_jwt_with_secret FOR TESTING.
10 |
11 | ENDCLASS.
12 |
13 | CLASS ltcl_test IMPLEMENTATION.
14 |
15 |
16 | METHOD generate_jwt_with_secret.
17 |
18 | DATA:
19 | BEGIN OF ls_jwt,
20 | header TYPE string,
21 | payload TYPE string,
22 | secret TYPE string,
23 | END OF ls_jwt.
24 |
25 | DATA(lo_jwt_generator) = NEW zcl_jwt_generator( ).
26 |
27 | TRY.
28 | DATA(lv_jwt) =
29 | lo_jwt_generator->generate_jwt_with_secret(
30 | jwt_header = VALUE #( alg = 'HS256' typ = 'JWT' )
31 | jwt_claim = VALUE #( sub = '1234567890' name = 'John Doe' iat = lo_jwt_generator->convert_abap_timestamp_to_unix( '20180118013022' ) )
32 | secret = '1WRAv0Usf90-jr2W7UQBQBLvIBBFq8vumq-VzrR3h7E'
33 | algorithm = 'SHA256' ).
34 | CATCH zcx_jwt_generator INTO DATA(lx_jwt).
35 | cl_abap_unit_assert=>fail( msg = lx_jwt->get_text( ) detail = lx_jwt->get_longtext( ) ).
36 | ENDTRY.
37 |
38 | SPLIT lv_jwt AT '.' INTO ls_jwt-header ls_jwt-payload ls_jwt-secret.
39 |
40 | cl_abap_unit_assert=>assert_equals(
41 | act = ls_jwt-header
42 | exp = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
43 | msg = 'Header has invalid content' ).
44 |
45 | cl_abap_unit_assert=>assert_equals(
46 | act = ls_jwt-payload
47 | exp = 'eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoiMTUxNjIzOTAyMiJ9'
48 | msg = 'Payload has invalid content' ).
49 |
50 | cl_abap_unit_assert=>assert_equals(
51 | act = ls_jwt-secret
52 | exp = '_GNK9PCjJnoeHZsNx9F-7TnZF8m_jS4lodaNe_w94MM'
53 | msg = 'Secret has invalid content' ).
54 |
55 | ENDMETHOD.
56 |
57 |
58 | ENDCLASS.
59 |
--------------------------------------------------------------------------------
/src/zcl_jwt_generator.clas.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ZCL_JWT_GENERATOR
7 | E
8 | Json Web Token Generator
9 | 1
10 | X
11 | X
12 | X
13 | X
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/zcx_jwt_generator.clas.abap:
--------------------------------------------------------------------------------
1 | CLASS zcx_jwt_generator DEFINITION
2 | PUBLIC
3 | INHERITING FROM cx_static_check
4 | FINAL
5 | CREATE PUBLIC .
6 |
7 | PUBLIC SECTION.
8 |
9 | INTERFACES if_t100_dyn_msg .
10 | INTERFACES if_t100_message .
11 |
12 | DATA:
13 | text1 TYPE string READ-ONLY,
14 | text2 TYPE string READ-ONLY,
15 | text3 TYPE string READ-ONLY,
16 | text4 TYPE string READ-ONLY.
17 |
18 | CLASS-METHODS raise_system
19 | RAISING
20 | zcx_jwt_generator.
21 |
22 | METHODS constructor
23 | IMPORTING
24 | !textid LIKE if_t100_message=>t100key OPTIONAL
25 | !text1 TYPE string OPTIONAL
26 | !text2 TYPE string OPTIONAL
27 | !text3 TYPE string OPTIONAL
28 | !text4 TYPE string OPTIONAL
29 | !previous LIKE previous OPTIONAL.
30 |
31 | PROTECTED SECTION.
32 |
33 | PRIVATE SECTION.
34 |
35 | ENDCLASS.
36 |
37 |
38 |
39 | CLASS zcx_jwt_generator IMPLEMENTATION.
40 |
41 |
42 | METHOD raise_system.
43 |
44 | RAISE EXCEPTION TYPE zcx_jwt_generator
45 | EXPORTING
46 | textid = VALUE scx_t100key( msgid = sy-msgid
47 | msgno = sy-msgno
48 | attr1 = 'TEXT1'
49 | attr2 = 'TEXT2'
50 | attr3 = 'TEXT3'
51 | attr4 = 'TEXT4' )
52 | text1 = CONV #( sy-msgv1 )
53 | text2 = CONV #( sy-msgv2 )
54 | text3 = CONV #( sy-msgv3 )
55 | text4 = CONV #( sy-msgv4 ).
56 |
57 | ENDMETHOD.
58 |
59 |
60 | METHOD constructor ##ADT_SUPPRESS_GENERATION.
61 |
62 | super->constructor( previous = previous ).
63 |
64 | me->text1 = text1.
65 | me->text2 = text2.
66 | me->text3 = text3.
67 | me->text4 = text4.
68 |
69 | CLEAR me->textid.
70 | IF textid IS INITIAL.
71 | if_t100_message~t100key = if_t100_message=>default_textid.
72 | ELSE.
73 | if_t100_message~t100key = textid.
74 | ENDIF.
75 |
76 | ENDMETHOD.
77 |
78 |
79 | ENDCLASS.
80 |
--------------------------------------------------------------------------------
/src/zcx_jwt_generator.clas.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ZCX_JWT_GENERATOR
7 | E
8 | Generator exception
9 | 40
10 | 1
11 | X
12 | X
13 | X
14 |
15 |
16 |
17 | ZCX_JWT_GENERATOR
18 | CONSTRUCTOR
19 | E
20 | CONSTRUCTOR
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/zjwt_profile.tabl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ZJWT_PROFILE
7 | E
8 | TRANSP
9 | Json Web Token Profile
10 | X
11 | C
12 | 1
13 |
14 |
15 | ZJWT_PROFILE
16 | A
17 | 0
18 | APPL0
19 | N
20 | R
21 |
22 |
23 |
24 | ZJWT_PROFILE
25 | PROFILE_NAME
26 | 0001
27 | X
28 | CHAR40
29 | 0
30 | X
31 | E
32 |
33 |
34 | ZJWT_PROFILE
35 | ISS
36 | E
37 | 0002
38 | 0
39 | g
40 | 000008
41 | STRG
42 | STRG
43 | Issuer
44 |
45 |
46 | ZJWT_PROFILE
47 | SUB
48 | E
49 | 0003
50 | 0
51 | g
52 | 000008
53 | STRG
54 | STRG
55 | subject
56 |
57 |
58 | ZJWT_PROFILE
59 | AUD
60 | E
61 | 0004
62 | 0
63 | g
64 | 000008
65 | STRG
66 | STRG
67 | Audience
68 |
69 |
70 | ZJWT_PROFILE
71 | TIME_INTERVAL
72 | E
73 | 0005
74 | 0
75 | 8
76 | 000008
77 | INT8
78 | 000019
79 | INT8
80 |
81 |
82 | ZJWT_PROFILE
83 | SSF_ID
84 | 0006
85 | SSFID
86 | 0
87 | E
88 |
89 |
90 | ZJWT_PROFILE
91 | SSF_PROFILE
92 | 0007
93 | SSFPROF
94 | 0
95 | E
96 |
97 |
98 | ZJWT_PROFILE
99 | ALG
100 | E
101 | 0008
102 | 0
103 | g
104 | 000008
105 | STRG
106 | STRG
107 | Encryption Algorithm
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------