From d50ab23ad1a967063de21aaa963eea9d9d1bd019 Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Wed, 20 Nov 2024 17:06:09 +0530 Subject: [PATCH 1/9] initial commit --- .../pom.xml | 230 ++++++++++++++++++ .../apim/backendauth/PKJWTAuthHandler.java | 68 ++++++ .../internal/PKJWTAuthServiceComponent.java | 4 + product-accelerators/pom.xml | 1 + 4 files changed, 303 insertions(+) create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PKJWTAuthHandler.java create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/internal/PKJWTAuthServiceComponent.java diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml new file mode 100644 index 0000000..4ba5c91 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml @@ -0,0 +1,230 @@ + + + + 4.0.0 + + + org.wso2.healthcare + product-accelerators + 1.0.0 + ../../../pom.xml + + + org.wso2.healthcare.apim.backendauth + 1.0.0 + bundle + + + + + + + + + + + + + + org.apache.ws.commons.axiom + axiom-api + + + commons-logging + commons-logging + 1.3.3 + + + org.apache.synapse + synapse-core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + UTF-8 + 1.8 + 1.8 + + + + + org.apache.felix + maven-bundle-plugin + true + 5.1.1 + + + ${project.artifactId}.${project.version} + ${project.artifactId} + + !org.wso2.healthcare.apim.backendauth.internal, + org.wso2.healthcare.apim.backendauth.*; + + + * + synapse-core + + + + + + + diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PKJWTAuthHandler.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PKJWTAuthHandler.java new file mode 100644 index 0000000..9d13324 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PKJWTAuthHandler.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth; + +import org.apache.synapse.MessageContext; +import org.apache.synapse.mediators.AbstractMediator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class PKJWTAuthHandler extends AbstractMediator { + + private static final Log log = LogFactory.getLog(PKJWTAuthHandler.class); + private String authType = "pkjwt"; + private String serverType = "epic"; + + public PKJWTAuthHandler(){} + + public boolean mediate(MessageContext mc) { + // Do somthing useful.. + // Implementation of Reading the propertly values of Message context and modifying request / logging properties + log.info("PKJWTAuthHandler mediator is invoked"); + return true; + } + + public String getType() { + return null; + } + + public void setTraceState(int traceState) { + traceState = 0; + } + + public int getTraceState() { + return 0; + } + + public void setAuthType(String newValue) { + authType=newValue; + } + + public String getAuthType() { + return authType; + } + + public void setServerType(String newValue) { + serverType=newValue; + } + + public String getServerType() { + return serverType; + } +} \ No newline at end of file diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/internal/PKJWTAuthServiceComponent.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/internal/PKJWTAuthServiceComponent.java new file mode 100644 index 0000000..9dc5100 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/internal/PKJWTAuthServiceComponent.java @@ -0,0 +1,4 @@ +package org.wso2.healthcare.apim.backendauth.internal; + +public class PKJWTAuthServiceComponent { +} diff --git a/product-accelerators/pom.xml b/product-accelerators/pom.xml index 4fba708..7969baa 100644 --- a/product-accelerators/pom.xml +++ b/product-accelerators/pom.xml @@ -42,6 +42,7 @@ apim/components/org.wso2.healthcare.apim.clientauth.jwt apim/components/org.wso2.healthcare.apim.scopemgt apim/components/org.wso2.healthcare.apim.tokenmgt + apim/components/org.wso2.healthcare.apim.backendauth apim/apps/authentication-portal apim/apps/recovery-portal From e018c1593a90aa6bba479e420e7486d9b7c33d36 Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Tue, 26 Nov 2024 20:26:33 +0530 Subject: [PATCH 2/9] add configs and installation steps --- distribution/apim-accelerator/bin/merge.sh | 9 ++ distribution/apim-accelerator/pom.xml | 12 +++ .../resources/repository/conf/deployment.toml | 10 ++ .../definitions/backendAuthPKJWT_v1.j2 | 2 + .../specifications/backendAuthPKJWT_v1.json | 18 ++++ .../apim/core/config/BackendAuthConfig.java | 95 +++++++++++++++++++ .../core/config/OpenHealthcareConfig.java | 32 +++++++ 7 files changed, 178 insertions(+) create mode 100644 distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/backendAuthPKJWT_v1.j2 create mode 100644 distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/backendAuthPKJWT_v1.json create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java diff --git a/distribution/apim-accelerator/bin/merge.sh b/distribution/apim-accelerator/bin/merge.sh index fbddfd7..e15055b 100755 --- a/distribution/apim-accelerator/bin/merge.sh +++ b/distribution/apim-accelerator/bin/merge.sh @@ -229,6 +229,15 @@ if [ "${smart_on_fhir_enabled}" == "true" ]; then echo -e "\n#[healthcare.identity.claims]\n#patient_id_claim_uri = \"http://wso2.org/claims/patientId\"\n#patient_id_key = \"patientId\"\n#fhirUser_resource_url_context = \"/r4/Patient\"\n#fhirUser_resource_id_claim_uri = \"http://wso2.org/claims/patientId\"" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null fi + if grep -Fxq "#[healthcare.backend.auth]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml || grep -Fxq "[healthcare.backend.auth]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml + then + # code if found + echo -e "[WARN] healthcare.backend.auth configuration already exist" + else + # code if not found + echo -e "\n#[healthcare.backend.auth]\n#enable = true\n#token_endpoint = \"https://localhost:9443/oauth2/token\"\n#client_id = \"client_id\"\n#keystore_password = \"wso2carbon\"\n#private_key_alias = \"wso2carbon\"\n#is_ssl_enabled = true\n#truststore_password = \"wso2carbon\"\n#ssl_cert_alias = \"wso2carbon\"" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null + fi + if grep -Fxq "#[healthcare.identity.claim.mgt]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml || grep -Fxq "[healthcare.identity.claim.mgt]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml then # code if found diff --git a/distribution/apim-accelerator/pom.xml b/distribution/apim-accelerator/pom.xml index 9cdaa79..780b7e6 100644 --- a/distribution/apim-accelerator/pom.xml +++ b/distribution/apim-accelerator/pom.xml @@ -157,6 +157,10 @@ + + + + @@ -279,6 +283,14 @@ excludes="**/velocity_template.xml"/> + + + + + diff --git a/distribution/apim-accelerator/resources/repository/conf/deployment.toml b/distribution/apim-accelerator/resources/repository/conf/deployment.toml index e28a2a1..6c52be0 100644 --- a/distribution/apim-accelerator/resources/repository/conf/deployment.toml +++ b/distribution/apim-accelerator/resources/repository/conf/deployment.toml @@ -418,6 +418,16 @@ policy.limit.time_unit = "min" patient_id_uri = "http://wso2.org/claims/patientId" patient_id_key = "patientId" +[healthcare.backend.auth] +enable = true +token_endpoint = "https://localhost:9443/oauth2/token" +client_id = "client_id" +keystore_password = "wso2carbon" +private_key_alias = "wso2carbon" +is_ssl_enabled = true +truststore_password = "wso2carbon" +ssl_cert_alias = "wso2carbon" + #[healthcare.deployment.webapps] #name = "Open Healthcare" #name_container_css = "" diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/backendAuthPKJWT_v1.j2 b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/backendAuthPKJWT_v1.j2 new file mode 100644 index 0000000..8a3471e --- /dev/null +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/backendAuthPKJWT_v1.j2 @@ -0,0 +1,2 @@ + + diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/backendAuthPKJWT_v1.json b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/backendAuthPKJWT_v1.json new file mode 100644 index 0000000..5fc3ea1 --- /dev/null +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/backendAuthPKJWT_v1.json @@ -0,0 +1,18 @@ +{ + "category": "Mediation", + "name": "replaceBackendAuthTokenPKJWT", + "version": "v1", + "displayName": "Replace Backend Auth Token PKJWT", + "description": "This handler will replace backend Auth token with an external PKJWT", + "applicableFlows": [ + "request" + ], + "supportedGateways": [ + "Synapse" + ], + "supportedApiTypes": [ + "HTTP" + ], + "policyAttributes": [ + ] +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java new file mode 100644 index 0000000..7661eaa --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.core.config; + +public class BackendAuthConfig { + + private boolean enableBackendAuth; + private String authEndpoint; + private String clientId; + private char[] keystorePassword; + private char[] truststorePassword; + private boolean isSSLEnabled; + private String privateKeyAlias; + private String sslCertAlias; + + public boolean isEnableBackendAuth() { + return enableBackendAuth; + } + + public void setEnableBackendAuth(boolean enableBackendAuth) { + this.enableBackendAuth = enableBackendAuth; + } + + public String getAuthEndpoint() { + return authEndpoint; + } + + public void setAuthEndpoint(String authEndpoint) { + this.authEndpoint = authEndpoint; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public char[] getKeystorePassword() { + return keystorePassword; + } + + public void setKeystorePassword(char[] keystorePassword) { + this.keystorePassword = keystorePassword; + } + + public char[] getTruststorePassword() { + return truststorePassword; + } + + public void setTruststorePassword(char[] truststorePassword) { + this.truststorePassword = truststorePassword; + } + + public boolean isSSLEnabled() { + return isSSLEnabled; + } + + public void setSSLEnabled(boolean SSLEnabled) { + isSSLEnabled = SSLEnabled; + } + + public String getPrivateKeyAlias() { + return privateKeyAlias; + } + + public void setPrivateKeyAlias(String privateKeyAlias) { + this.privateKeyAlias = privateKeyAlias; + } + + public String getSslCertAlias() { + return sslCertAlias; + } + + public void setSslCertAlias(String sslCertAlias) { + this.sslCertAlias = sslCertAlias; + } +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java index 5ede4ce..1f3609a 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java @@ -71,6 +71,7 @@ public class OpenHealthcareConfig { private Map notificationMailConfig; private OrganizationConfig organizationConfig; private ScopeMgtConfig scopeMgtConfig; + private BackendAuthConfig backendAuthConfig; private OpenHealthcareConfig(TomlParseResult config) throws OpenHealthcareException { this.config = config; @@ -153,6 +154,10 @@ public ScopeMgtConfig getScopeMgtConfig() { return scopeMgtConfig; } + public BackendAuthConfig getBackendAuthConfig() { + return backendAuthConfig; + } + private void parse(TomlParseResult config) throws OpenHealthcareException { secretResolver = SecretResolverFactory.create((OMElement) null, false); @@ -183,6 +188,8 @@ private void parse(TomlParseResult config) throws OpenHealthcareException { organizationConfig = buildOrganizationconfig(); //Parse scope mgt config scopeMgtConfig = buildScopeMgtConfig(); + //Parse backend auth config + backendAuthConfig = buildBackendAuthConfig(); } private AccountConfig buildAccountConfig() throws OpenHealthcareException { @@ -572,6 +579,31 @@ private ScopeMgtConfig buildScopeMgtConfig() { } + private BackendAuthConfig buildBackendAuthConfig() { + BackendAuthConfig backendAuthConfig = new BackendAuthConfig(); + Object backendAuthObj = config.get("healthcare.backend.auth"); + if (backendAuthObj instanceof TomlTable) { + TomlTable beAuthTable = (TomlTable) backendAuthObj; + backendAuthConfig.setEnableBackendAuth(Boolean.TRUE.equals(beAuthTable.getBoolean("enable"))); + backendAuthConfig.setAuthEndpoint(beAuthTable.getString("token_endpoint")); + backendAuthConfig.setClientId(beAuthTable.getString("client_id")); + backendAuthConfig.setPrivateKeyAlias(beAuthTable.getString("private_key_alias")); + backendAuthConfig.setSSLEnabled(Boolean.TRUE.equals(beAuthTable.getBoolean("is_ssl_enabled"))); + String privateKeyPass = beAuthTable.getString("keystore_password", () -> null); + if (privateKeyPass != null) { + backendAuthConfig.setTruststorePassword(resolveSecret(privateKeyPass)); + } + if (backendAuthConfig.isSSLEnabled()) { + String secretKey = beAuthTable.getString("truststore_password", () -> null); + if (secretKey != null) { + backendAuthConfig.setTruststorePassword(resolveSecret(secretKey)); + } + backendAuthConfig.setSslCertAlias(beAuthTable.getString("ssl_cert_alias")); + } + } + return backendAuthConfig; + } + private char[] resolveSecret(String secret) { String protectedToken = MiscellaneousUtil.getProtectedToken(secret); if (protectedToken != null) { From 89d0fb3de56a09fa89953950af287be80e4c2fd1 Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Fri, 29 Nov 2024 10:51:49 +0530 Subject: [PATCH 3/9] address review comments --- distribution/apim-accelerator/bin/merge.sh | 2 +- .../resources/repository/conf/deployment.toml | 5 +- .../definitions/backendAuthPKJWT_v1.j2 | 2 - .../definitions/replaceBackendAuthToken_v1.j2 | 3 + .../specifications/backendAuthPKJWT_v1.json | 18 - .../replaceBackendAuthToken_v1.json | 27 ++ .../pom.xml | 152 +-------- .../readme.md | 13 + .../backendauth/BackendAuthenticator.java | 83 +++++ .../apim/backendauth/Constants.java | 39 +++ .../apim/backendauth/PKJWTAuthHandler.java | 68 ---- .../PrivateKeyJWTBackendAuthenticator.java | 313 ++++++++++++++++++ .../healthcare/apim/backendauth/Utils.java | 203 ++++++++++++ .../internal/PKJWTAuthServiceComponent.java | 4 - .../tokenmgt/InMemoryTokenStore.java | 51 +++ .../apim/backendauth/tokenmgt/Token.java | 61 ++++ .../backendauth/tokenmgt/TokenManager.java | 70 ++++ .../apim/backendauth/tokenmgt/TokenStore.java | 45 +++ .../apim/core/config/BackendAuthConfig.java | 40 +-- .../core/config/OpenHealthcareConfig.java | 14 +- 20 files changed, 925 insertions(+), 288 deletions(-) delete mode 100644 distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/backendAuthPKJWT_v1.j2 create mode 100644 distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 delete mode 100644 distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/backendAuthPKJWT_v1.json create mode 100644 distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java delete mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PKJWTAuthHandler.java create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java delete mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/internal/PKJWTAuthServiceComponent.java create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/Token.java create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenManager.java create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenStore.java diff --git a/distribution/apim-accelerator/bin/merge.sh b/distribution/apim-accelerator/bin/merge.sh index e15055b..7ef2dc2 100755 --- a/distribution/apim-accelerator/bin/merge.sh +++ b/distribution/apim-accelerator/bin/merge.sh @@ -235,7 +235,7 @@ if [ "${smart_on_fhir_enabled}" == "true" ]; then echo -e "[WARN] healthcare.backend.auth configuration already exist" else # code if not found - echo -e "\n#[healthcare.backend.auth]\n#enable = true\n#token_endpoint = \"https://localhost:9443/oauth2/token\"\n#client_id = \"client_id\"\n#keystore_password = \"wso2carbon\"\n#private_key_alias = \"wso2carbon\"\n#is_ssl_enabled = true\n#truststore_password = \"wso2carbon\"\n#ssl_cert_alias = \"wso2carbon\"" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null + echo -e "\n[healthcare.backend.auth]\ntoken_endpoint = \"https://localhost:9443/oauth2/token\"\nclient_id = \"client_id\"\nclient_secret = \"client_secret\"\nprivate_key_alias = \"wso2carbon\"\nis_ssl_enabled = true" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null fi if grep -Fxq "#[healthcare.identity.claim.mgt]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml || grep -Fxq "[healthcare.identity.claim.mgt]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml diff --git a/distribution/apim-accelerator/resources/repository/conf/deployment.toml b/distribution/apim-accelerator/resources/repository/conf/deployment.toml index 6c52be0..4b2a238 100644 --- a/distribution/apim-accelerator/resources/repository/conf/deployment.toml +++ b/distribution/apim-accelerator/resources/repository/conf/deployment.toml @@ -419,14 +419,11 @@ patient_id_uri = "http://wso2.org/claims/patientId" patient_id_key = "patientId" [healthcare.backend.auth] -enable = true token_endpoint = "https://localhost:9443/oauth2/token" client_id = "client_id" -keystore_password = "wso2carbon" +client_secret = "client_secret" private_key_alias = "wso2carbon" is_ssl_enabled = true -truststore_password = "wso2carbon" -ssl_cert_alias = "wso2carbon" #[healthcare.deployment.webapps] #name = "Open Healthcare" diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/backendAuthPKJWT_v1.j2 b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/backendAuthPKJWT_v1.j2 deleted file mode 100644 index 8a3471e..0000000 --- a/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/backendAuthPKJWT_v1.j2 +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 new file mode 100644 index 0000000..17cc010 --- /dev/null +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 @@ -0,0 +1,3 @@ + + + diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/backendAuthPKJWT_v1.json b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/backendAuthPKJWT_v1.json deleted file mode 100644 index 5fc3ea1..0000000 --- a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/backendAuthPKJWT_v1.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "category": "Mediation", - "name": "replaceBackendAuthTokenPKJWT", - "version": "v1", - "displayName": "Replace Backend Auth Token PKJWT", - "description": "This handler will replace backend Auth token with an external PKJWT", - "applicableFlows": [ - "request" - ], - "supportedGateways": [ - "Synapse" - ], - "supportedApiTypes": [ - "HTTP" - ], - "policyAttributes": [ - ] -} diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json new file mode 100644 index 0000000..b604b1f --- /dev/null +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json @@ -0,0 +1,27 @@ +{ + "category": "Mediation", + "name": "replaceBackendAuthToken", + "version": "v1", + "displayName": "Replace Backend Auth Token", + "description": "This handler will replace backend Auth token with a token from external Auth server", + "applicableFlows": [ + "request" + ], + "supportedGateways": [ + "Synapse" + ], + "supportedApiTypes": [ + "HTTP" + ], + "policyAttributes": [ + { + "name": "authType", + "displayName": "Auth Type", + "description": "Type of Backend Auth Flow. Currently only PKJWT is supported", + "validationRegex": "^(pkjwt|client-credentials)$", + "type": "Enum", + "allowedValues" : ["pkjwt"], + "required": true + } + ] +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml index 4ba5c91..c2f9021 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml @@ -33,16 +33,6 @@ bundle - - - - - - - - - - org.apache.ws.commons.axiom axiom-api @@ -56,142 +46,10 @@ org.apache.synapse synapse-core - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + org.wso2.healthcare + org.wso2.healthcare.apim.core + @@ -215,7 +73,6 @@ ${project.artifactId}.${project.version} ${project.artifactId} - !org.wso2.healthcare.apim.backendauth.internal, org.wso2.healthcare.apim.backendauth.*; @@ -226,5 +83,4 @@ - diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md new file mode 100644 index 0000000..c082df1 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md @@ -0,0 +1,13 @@ +# Backend Authentication for WSO2 API Manager + +## Configuration Model + +```toml +[healthcare.backend.auth] +token_endpoint = "https://localhost:9443/oauth2/token" +client_id = "client_id" +client_secret = "client_secret" +private_key_alias = "wso2carbon" +is_ssl_enabled = true +``` + diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java new file mode 100644 index 0000000..0e30bad --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.synapse.MessageContext; +import org.apache.synapse.mediators.AbstractMediator; +import org.wso2.healthcare.apim.core.OpenHealthcareEnvironment; +import org.wso2.healthcare.apim.core.OpenHealthcareException; +import org.wso2.healthcare.apim.core.OpenHealthcareRuntimeException; +import org.wso2.healthcare.apim.core.config.BackendAuthConfig; + +/** + * Backend authenticator mediator. + */ +public class BackendAuthenticator extends AbstractMediator { + + private static final Log log = LogFactory.getLog(BackendAuthenticator.class); + private String authType; + protected final BackendAuthConfig backendAuthConfig; + + public BackendAuthenticator() throws OpenHealthcareException { + backendAuthConfig = OpenHealthcareEnvironment.getInstance().getConfig().getBackendAuthConfig(); + } + @Override + public boolean mediate(MessageContext messageContext) { + if (log.isDebugEnabled()) { + log.debug("Backend authenticator mediator is invoked."); + } + + if (authType == null) { + log.error("Auth type is not defined in the message context."); + return false; + } + if (authType.equals(Constants.POLICY_ATTR_AUTH_TYPE_PKJWT)) { + if (log.isDebugEnabled()) { + log.debug("Auth type is PKJWT."); + } + PrivateKeyJWTBackendAuthenticator privateKeyJWTBackendAuthenticator; + try { + privateKeyJWTBackendAuthenticator = new PrivateKeyJWTBackendAuthenticator(); + } catch (OpenHealthcareException e) { + log.error("Error occurred while initializing the private key JWT backend authenticator.", e); + throw new OpenHealthcareRuntimeException(e); + } + privateKeyJWTBackendAuthenticator.mediate(messageContext); + return true; + } else if (authType.equals(Constants.POLICY_ATTR_AUTH_TYPE_CLIENT_CRED)) { + if (log.isDebugEnabled()) { + log.debug("Auth type is CLIENT CREDENTIALS."); + } + return true; + } else { + log.error("Auth type is not supported."); + return false; + } + } + + public String getAuthType() { + return authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java new file mode 100644 index 0000000..b4f79ee --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth; + +/** + * Constants class for the backend auth component. + */ +public class Constants { + public static final String TOKEN_KEY_SEPARATOR = "_"; + public static final String PROPERTY_ACCESS_TOKEN = "_HC_INTERNAL_ACCESS_TOKEN_"; + + public static final int BAD_REQUEST_ERROR_CODE = 400; + public static final int INTERNAL_SERVER_ERROR_CODE = 500; + public static final String HTTP_SC = "HTTP_SC"; + public static final String TENANT_INFO_ID = "tenant.info.id"; + public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 2; + public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; + public static final String HEADER_NAME_AUTHORIZATION = "Authorization"; + public static final String HEADER_VALUE_BEARER = "Bearer "; + + public static final String POLICY_ATTR_AUTH_TYPE_CLIENT_CRED = "client-credentials"; + public static final String POLICY_ATTR_AUTH_TYPE_PKJWT = "pkjwt"; +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PKJWTAuthHandler.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PKJWTAuthHandler.java deleted file mode 100644 index 9d13324..0000000 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PKJWTAuthHandler.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.wso2.healthcare.apim.backendauth; - -import org.apache.synapse.MessageContext; -import org.apache.synapse.mediators.AbstractMediator; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -public class PKJWTAuthHandler extends AbstractMediator { - - private static final Log log = LogFactory.getLog(PKJWTAuthHandler.class); - private String authType = "pkjwt"; - private String serverType = "epic"; - - public PKJWTAuthHandler(){} - - public boolean mediate(MessageContext mc) { - // Do somthing useful.. - // Implementation of Reading the propertly values of Message context and modifying request / logging properties - log.info("PKJWTAuthHandler mediator is invoked"); - return true; - } - - public String getType() { - return null; - } - - public void setTraceState(int traceState) { - traceState = 0; - } - - public int getTraceState() { - return 0; - } - - public void setAuthType(String newValue) { - authType=newValue; - } - - public String getAuthType() { - return authType; - } - - public void setServerType(String newValue) { - serverType=newValue; - } - - public String getServerType() { - return serverType; - } -} \ No newline at end of file diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java new file mode 100644 index 0000000..e829748 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.apache.synapse.MessageContext; +import org.apache.synapse.core.axis2.Axis2MessageContext; +import org.wso2.carbon.core.util.KeyStoreManager; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.healthcare.apim.backendauth.tokenmgt.Token; +import org.wso2.healthcare.apim.backendauth.tokenmgt.TokenManager; +import org.wso2.healthcare.apim.core.OpenHealthcareException; +import org.wso2.healthcare.apim.core.OpenHealthcareRuntimeException; +import org.wso2.healthcare.apim.core.config.BackendAuthConfig; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.security.Key; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.ArrayList; +import java.util.Date; +import java.util.Map; +import java.util.UUID; + +/** + * Mediator to authenticate with the backend using private key JWT. + */ +public class PrivateKeyJWTBackendAuthenticator extends BackendAuthenticator { + + private static final Log log = LogFactory.getLog(PrivateKeyJWTBackendAuthenticator.class); + private static final JsonParser parser = new JsonParser(); + + public PrivateKeyJWTBackendAuthenticator() throws OpenHealthcareException { + } + + public boolean mediate(MessageContext messageContext) { + + log.info("PrivateKeyJWTBackendAuthenticator mediator is started."); + + String accessToken; + String tokenEndpoint = backendAuthConfig.getAuthEndpoint(); + String keyAlias = backendAuthConfig.getPrivateKeyAlias(); + char[] keyStorePass = CarbonUtils.getServerConfiguration().getFirstProperty( + "Security.KeyStore.Password").toCharArray(); + char[] trustStorePass = CarbonUtils.getServerConfiguration().getFirstProperty( + "Security.TrustStore.Password").toCharArray(); + String clientId = backendAuthConfig.getClientId(); + int tenantId = Integer.parseInt(messageContext.getProperty(Constants.TENANT_INFO_ID).toString()); + KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); + Key privateKey; + Certificate publicCert; + + if (log.isDebugEnabled()) { + log.debug("Configured client ID: " + clientId); + } + + try { + KeyStore primarykeyStore = keyStoreManager.getPrimaryKeyStore(); + privateKey = primarykeyStore.getKey(keyAlias, keyStorePass); + publicCert = keyStoreManager.getDefaultPrimaryCertificate(); + } catch (Exception e) { + log.error("Error occurred while retrieving private key from keystore.", e); + throw new OpenHealthcareRuntimeException(e); + } + + Token token = TokenManager.getToken(clientId, tokenEndpoint); + if (token == null || !token.isActive()) { + if (log.isDebugEnabled()) { + log.debug("Access token not available in TokenManager."); + } + try { + token = getAndAddNewToken(clientId, tokenEndpoint, messageContext, privateKey, publicCert, trustStorePass); + } catch (OpenHealthcareException e) { + log.error("Error occurred while retrieving access token.",e); + throw new OpenHealthcareRuntimeException(e); + } + } + accessToken = token.getAccessToken(); + + messageContext.setProperty(Constants.PROPERTY_ACCESS_TOKEN, accessToken); + + if (messageContext instanceof Axis2MessageContext) { + org.apache.axis2.context.MessageContext axisMsgCtx = + ((Axis2MessageContext) messageContext).getAxis2MessageContext(); + Object headers = axisMsgCtx.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); + if (headers instanceof Map) { + Map headersMap = (Map) headers; + if (headersMap.get(Constants.HEADER_NAME_AUTHORIZATION) != null) { + headersMap.remove(Constants.HEADER_NAME_AUTHORIZATION); + } + headersMap.put(Constants.HEADER_NAME_AUTHORIZATION, Constants.HEADER_VALUE_BEARER + accessToken); + axisMsgCtx.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headersMap); + }else { + log.warn("Transport headers are not available in the message context."); + } + }else { + log.error("Message context is not an instance of Axis2MessageContext."); + } + return true; + } + + /** + * Retrieve token from the token store. If token is not available or inactive, generate new token. + * @param clientId - to retrieve token + * @param tokenEndpoint - to retrieve token + * @param messageContext - to set error response + * @param privateKey - to sign the JWT + * @return Token + * @throws OpenHealthcareException - when error occurred while retrieving token + */ + private synchronized Token getAndAddNewToken(String clientId, String tokenEndpoint, MessageContext messageContext, Key privateKey, Certificate publicCert, char[] trustStorePass) throws OpenHealthcareException { + + Token token = TokenManager.getToken(clientId, tokenEndpoint); + if (token == null || !token.isActive()) { + String jwt = generateJWT(clientId, tokenEndpoint, privateKey, publicCert); + token = getAccessToken(messageContext, tokenEndpoint, jwt, backendAuthConfig.isSSLEnabled(), trustStorePass); + TokenManager.addToken(clientId, tokenEndpoint, token); + }else { + if (log.isDebugEnabled()) { + log.debug("Token exists in the token store."); + } + } + return token; + } + + /** + * Generate JWT token with signature. + * @param clientId - to add as subject + * @param tokenEndpoint - audience + * @param privateKey - to sign the JWT + * @return JWT as string + * @throws OpenHealthcareException JOSEException + */ + private String generateJWT(String clientId, String tokenEndpoint, Key privateKey, Certificate publicCert) throws OpenHealthcareException { + + try { + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey; + JWSSigner signer = new RSASSASigner(rsaPrivateKey); + long curTimeInMillis = System.currentTimeMillis(); + + JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder(); + claimsSetBuilder.issuer(clientId); + claimsSetBuilder.subject(clientId); + claimsSetBuilder.audience(tokenEndpoint); + claimsSetBuilder.jwtID(UUID.randomUUID().toString()); + claimsSetBuilder.issueTime((new Date(curTimeInMillis))); + claimsSetBuilder.notBeforeTime((new Date(curTimeInMillis))); + claimsSetBuilder.expirationTime(new Date(curTimeInMillis + 300000)); // Maximum expiration time is 5 min. + JWTClaimsSet claimsSet = claimsSetBuilder.build(); + + JWSAlgorithm signatureAlgorithm = new JWSAlgorithm(JWSAlgorithm.RS256.getName()); + JWSHeader.Builder headerBuilder = new JWSHeader.Builder(signatureAlgorithm); + headerBuilder.type(JOSEObjectType.JWT); + headerBuilder.keyID(Utils.getKID((X509Certificate) publicCert)); + JWSHeader jwsHeader = headerBuilder.build(); + + SignedJWT signedJWT = new SignedJWT(jwsHeader, claimsSet); + signedJWT.sign(signer); + return signedJWT.serialize(); + + } catch (JOSEException e) { + String message = "Error occurred while signing the JWT."; + log.error(message, e); + throw new OpenHealthcareException(message, e); + } + } + + /** + * Retrieve access token from the token endpoint. This makes external HTTP call to the token endpoint. + * @param messageContext - to set error response + * @param tokenEndpoint - to retrieve access token + * @param jwt - signed JWT including claims + * @param isSSLEnabled - whether SSL is enabled + * @return Token + * @throws OpenHealthcareException - when error occurred while retrieving access token + */ + private static Token getAccessToken( + MessageContext messageContext, + String tokenEndpoint, + String jwt, + boolean isSSLEnabled, + char[] trustStorePass) + throws OpenHealthcareException { + + long curTimeInMillis = System.currentTimeMillis(); + String trustStorePath = System.getProperty("javax.net.ssl.trustStore"); + HttpPost postRequest = new HttpPost(tokenEndpoint); + ArrayList parameters = new ArrayList<>(); + parameters.add(new BasicNameValuePair("grant_type", "client_credentials")); + parameters.add(new BasicNameValuePair("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")); + parameters.add(new BasicNameValuePair("client_assertion", jwt)); + + try { + postRequest.setEntity(new UrlEncodedFormEntity(parameters)); + } catch (UnsupportedEncodingException e) { + String message = "Error occurred while preparing access token request payload."; + log.error(message); + throw new OpenHealthcareException(message, e); + } + CloseableHttpClient httpsClient; + + if (isSSLEnabled) { + httpsClient = Utils.getHttpsClient(trustStorePath, trustStorePass); + } else { + httpsClient = HttpClients.createDefault(); + } + CloseableHttpResponse response; + try { + response = httpsClient.execute(postRequest); + HttpEntity responseEntity = response.getEntity(); + if (responseEntity == null) { + String message = "Failed to retrieve access token : No entity received."; + log.error(message); + throw new OpenHealthcareException(message); + } + int responseStatus = response.getStatusLine().getStatusCode(); + String respMessage; + respMessage = EntityUtils.toString(responseEntity); + + if (responseStatus == HttpURLConnection.HTTP_OK) { + return extractToken(respMessage, curTimeInMillis); + } else { + handleTokenRequestError(messageContext, responseStatus, respMessage); + } + } catch (IOException e) { + throw new OpenHealthcareRuntimeException(e); + } + throw new OpenHealthcareException("Error occurred while retrieving access token."); + } + + /** + * Handle error response from the token endpoint. + * @param messageContext - to set error response + * @param responseStatus - HTTP status code + * @param respMessage - response message + * @throws OpenHealthcareException - to propagate the error + */ + private static void handleTokenRequestError(MessageContext messageContext, int responseStatus, String respMessage) throws OpenHealthcareException { + String message = "Error occurred while retrieving access token. Response: [Status : " + responseStatus + " Message: " + respMessage + "]"; + log.error(message); + OpenHealthcareException exp = new OpenHealthcareException(message); + Utils.setErrorResponse(messageContext, exp, responseStatus, message); + throw exp; + } + + /** + * Extract token from the response message. + * @param respMessage - response message + * @param curTimeInMillis - current time in milliseconds + * @return - Token + */ + private static Token extractToken(String respMessage, long curTimeInMillis) { + JsonElement jsonElement = parser.parse(respMessage); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + String accessToken = jsonObject.get("access_token").getAsString(); + long expireIn = jsonObject.get("expires_in").getAsLong(); + + Token token = new Token(accessToken, curTimeInMillis, expireIn * 1000); + if (log.isDebugEnabled()) { + log.debug(token); + } + return token; + } + + public String getType() { + return null; + } + + public void setTraceState(int traceState) { + traceState = 0; + } + + public int getTraceState() { + return 0; + } +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java new file mode 100644 index 0000000..ea0d91a --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.conn.ssl.X509HostnameVerifier; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.ssl.TrustStrategy; +import org.apache.synapse.MessageContext; +import org.apache.synapse.SynapseConstants; +import org.apache.synapse.core.axis2.Axis2MessageContext; +import org.wso2.healthcare.apim.core.OpenHealthcareException; + +import javax.net.ssl.SSLContext; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * Utility class for the backend authentication. + */ +public class Utils { + private static final Log log = LogFactory.getLog(Utils.class); + public static final String ALLOW_ALL = "AllowAll"; + public static final String STRICT = "Strict"; + public static final String HOST_NAME_VERIFIER = "httpclient.hostnameVerifier"; + public static final String HTTP_PROTOCOL = "http"; + public static final String HTTPS_PROTOCOL = "https"; + private static final String[] SUPPORTED_HTTP_PROTOCOLS = {"TLSv1.2"}; + + public static void setErrorResponse(MessageContext messageContext, Throwable e, int errorCode, String msg) { + + messageContext.setProperty(SynapseConstants.ERROR_CODE, errorCode); + messageContext.setProperty(SynapseConstants.ERROR_MESSAGE, msg); + messageContext.setProperty(SynapseConstants.ERROR_DETAIL, e.getMessage()); + messageContext.setProperty(SynapseConstants.ERROR_EXCEPTION, e); + ((Axis2MessageContext) messageContext).getAxis2MessageContext().setProperty(Constants.HTTP_SC, errorCode); + } + + /** + * Get closeable https client. + * + * @return Closeable https client + * @throws OpenHealthcareException exception + */ + public static CloseableHttpClient getHttpsClient(String trustStorePath, char[] trustStorePass) throws OpenHealthcareException { + + SSLConnectionSocketFactory sslsf = createSSLConnectionSocketFactory(trustStorePath,trustStorePass); + + Registry socketFactoryRegistry = RegistryBuilder.create() + .register(HTTP_PROTOCOL, new PlainConnectionSocketFactory()) + .register(HTTPS_PROTOCOL, sslsf) + .build(); + + final PoolingHttpClientConnectionManager connectionManager = getPoolingHttpClientConnectionManager(socketFactoryRegistry); + + return HttpClients.custom().setConnectionManager(connectionManager).build(); + } + + /** + * Get the pooling http client connection manager. + * @param socketFactoryRegistry - Socket factory registry + * @return PoolingHttpClientConnectionManager + */ + private static PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager(Registry socketFactoryRegistry) { + final PoolingHttpClientConnectionManager connectionManager = (socketFactoryRegistry != null) ? + new PoolingHttpClientConnectionManager(socketFactoryRegistry) : + new PoolingHttpClientConnectionManager(); + + // configuring default maximum connections + // todo: make these values configurable: + connectionManager.setMaxTotal(Constants.DEFAULT_MAX_TOTAL_CONNECTIONS); + connectionManager.setDefaultMaxPerRoute(Constants.DEFAULT_MAX_CONNECTIONS_PER_ROUTE); + return connectionManager; + } + + /** + * create an SSL Connection Socket Factory. + * + * @return SSLConnectionSocketFactory + * @throws OpenHealthcareException when failed to create the SSL Connection Socket Factory + */ + private static SSLConnectionSocketFactory createSSLConnectionSocketFactory(String trustStorePath, char[] trustStorePassword) + throws OpenHealthcareException { + + KeyStore trustStore = null; + + trustStore = loadKeyStore( + trustStorePath, + trustStorePassword); + + // Trust own CA and all self-signed certs + SSLContext sslcontext; + try { + sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, (TrustStrategy) new TrustSelfSignedStrategy()).build(); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { + throw new OpenHealthcareException("Unable to create the ssl context", e); + } + + // Allow TLSv1 protocol only + return new SSLConnectionSocketFactory(sslcontext, SUPPORTED_HTTP_PROTOCOLS, + null, getX509HostnameVerifier()); + + } + + + /** + * Load the keystore when the location and password is provided. + * + * @param keyStoreLocation Location of the keystore + * @param keyStorePassword Keystore password + * @return Keystore as an object + * @throws OpenHealthcareException when failed to load Keystore from given details + */ + public static KeyStore loadKeyStore(String keyStoreLocation, char[] keyStorePassword) + throws OpenHealthcareException { + + KeyStore keyStore; + + try (FileInputStream inputStream = new FileInputStream(keyStoreLocation)) { + keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(inputStream, keyStorePassword); + return keyStore; + } catch (KeyStoreException e) { + throw new OpenHealthcareException("Error while retrieving aliases from keystore", e); + } catch (IOException | CertificateException | NoSuchAlgorithmException e) { + throw new OpenHealthcareException("Error while loading keystore", e); + } + } + + /** + * Get the Hostname Verifier property in set in system properties. + * + * @return X509HostnameVerifier + */ + public static X509HostnameVerifier getX509HostnameVerifier() { + + String hostnameVerifierOption = System.getProperty(HOST_NAME_VERIFIER); + X509HostnameVerifier hostnameVerifier; + + if (ALLOW_ALL.equals(hostnameVerifierOption)) { + hostnameVerifier = SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; + } else if (STRICT.equals(hostnameVerifierOption)) { + hostnameVerifier = SSLSocketFactory.STRICT_HOSTNAME_VERIFIER; + } else { + hostnameVerifier = SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; + } + + if (log.isDebugEnabled()) { + log.debug(String.format("Proceeding with %s : %s", HOST_NAME_VERIFIER, + hostnameVerifierOption)); + } + return hostnameVerifier; + + } + + /** + * Helper method to add kid claim into to JWT_HEADER. + * + * @param cert X509 certificate + * @return KID + */ + public static String getKID(X509Certificate cert) { + String serialNumber = cert.getSerialNumber().toString(); + String issuerName = cert.getIssuerDN().getName(); + String kid = issuerName + "#" + serialNumber; + return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(kid.getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/internal/PKJWTAuthServiceComponent.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/internal/PKJWTAuthServiceComponent.java deleted file mode 100644 index 9dc5100..0000000 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/internal/PKJWTAuthServiceComponent.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.wso2.healthcare.apim.backendauth.internal; - -public class PKJWTAuthServiceComponent { -} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java new file mode 100644 index 0000000..8083120 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth.tokenmgt; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class InMemoryTokenStore implements TokenStore { + + private final Map TOKEN_MAP = new ConcurrentHashMap<>(2); + + @Override + public Token get(String tokenKey) { + + return TOKEN_MAP.get(tokenKey); + } + + @Override + public void add(String tokenKey, Token token) { + + TOKEN_MAP.put(tokenKey, token); + } + + @Override + public Token remove(String tokenKey) { + + return TOKEN_MAP.remove(tokenKey); + } + + @Override + public void clean() { + + TOKEN_MAP.clear(); + } +} \ No newline at end of file diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/Token.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/Token.java new file mode 100644 index 0000000..30ac2c2 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/Token.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth.tokenmgt; + +public class Token { + + private final String accessToken; + private final Long createTimestamp; + private final Long expireIn; + + public Token(String accessToken, Long createTimestamp, Long expireIn) { + + this.accessToken = accessToken; + this.createTimestamp = createTimestamp; + this.expireIn = expireIn; + } + + public String getAccessToken() { + + return accessToken; + } + + public Long getExpireIn() { + + return expireIn; + } + + public Long getCreateTimestamp() { + + return createTimestamp; + } + + public boolean isActive() { + + long curTimeInMillis = System.currentTimeMillis(); + return (curTimeInMillis - createTimestamp) < expireIn; + } + + @Override + public String toString() { + + return "Token{" + "accessToken='" + accessToken + '\'' + ", createTimestamp=" + + createTimestamp + ", expireIn=" + expireIn + '}'; + } +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenManager.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenManager.java new file mode 100644 index 0000000..13a3aca --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenManager.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth.tokenmgt; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.healthcare.apim.backendauth.Constants; + +public class TokenManager { + private static final Log log = LogFactory.getLog(TokenManager.class); + private static final TokenStore TOKEN_STORE = new InMemoryTokenStore(); + + private TokenManager() { + + } + + /** + * Function to add access token for given client ID and token endpoint. + */ + public static void addToken(String clientId, String resourceKey, Token token) { + + String tokenKey = clientId + Constants.TOKEN_KEY_SEPARATOR + resourceKey; + TOKEN_STORE.add(tokenKey, token); + } + + /** + * Function to get access token for given client ID and token endpoint. + */ + public static Token getToken(String clientId, String resourceKey) { + + String tokenKey = clientId + Constants.TOKEN_KEY_SEPARATOR + resourceKey; + return TOKEN_STORE.get(tokenKey); + } + + /** + * Function to remove token from the token cache. + */ + public static void removeToken(String clientId, String resourceKey) { + + String tokenKey = clientId + Constants.TOKEN_KEY_SEPARATOR + resourceKey; + TOKEN_STORE.remove(tokenKey); + } + + /** + * Clean all access tokens from the token cache. + */ + public static void clean() { + + TOKEN_STORE.clean(); + if (log.isDebugEnabled()) { + log.debug("Token map cleaned."); + } + } +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenStore.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenStore.java new file mode 100644 index 0000000..8b48500 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenStore.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth.tokenmgt; + +/** + * Interface of Token Store. + */ +public interface TokenStore { + /** + * Function to get token. + */ + Token get(String tokenKey); + + /** + * Function to add token to the store. + */ + void add(String tokenKey, Token token); + + /** + * Function to remove token from the store. + */ + Token remove(String tokenKey); + + /** + * Function to clean token store. + */ + void clean(); + +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java index 7661eaa..b373a09 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java @@ -18,24 +18,16 @@ package org.wso2.healthcare.apim.core.config; +/** + * Backend Auth Config. To facilitate PKJWT backend auth flow + */ public class BackendAuthConfig { - private boolean enableBackendAuth; private String authEndpoint; private String clientId; - private char[] keystorePassword; - private char[] truststorePassword; + private char[] client_secret; private boolean isSSLEnabled; private String privateKeyAlias; - private String sslCertAlias; - - public boolean isEnableBackendAuth() { - return enableBackendAuth; - } - - public void setEnableBackendAuth(boolean enableBackendAuth) { - this.enableBackendAuth = enableBackendAuth; - } public String getAuthEndpoint() { return authEndpoint; @@ -53,20 +45,12 @@ public void setClientId(String clientId) { this.clientId = clientId; } - public char[] getKeystorePassword() { - return keystorePassword; - } - - public void setKeystorePassword(char[] keystorePassword) { - this.keystorePassword = keystorePassword; + public char[] getClient_secret() { + return client_secret; } - public char[] getTruststorePassword() { - return truststorePassword; - } - - public void setTruststorePassword(char[] truststorePassword) { - this.truststorePassword = truststorePassword; + public void setClient_secret(char[] client_secret) { + this.client_secret = client_secret; } public boolean isSSLEnabled() { @@ -84,12 +68,4 @@ public String getPrivateKeyAlias() { public void setPrivateKeyAlias(String privateKeyAlias) { this.privateKeyAlias = privateKeyAlias; } - - public String getSslCertAlias() { - return sslCertAlias; - } - - public void setSslCertAlias(String sslCertAlias) { - this.sslCertAlias = sslCertAlias; - } } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java index 1f3609a..94bff92 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java @@ -584,21 +584,13 @@ private BackendAuthConfig buildBackendAuthConfig() { Object backendAuthObj = config.get("healthcare.backend.auth"); if (backendAuthObj instanceof TomlTable) { TomlTable beAuthTable = (TomlTable) backendAuthObj; - backendAuthConfig.setEnableBackendAuth(Boolean.TRUE.equals(beAuthTable.getBoolean("enable"))); backendAuthConfig.setAuthEndpoint(beAuthTable.getString("token_endpoint")); backendAuthConfig.setClientId(beAuthTable.getString("client_id")); backendAuthConfig.setPrivateKeyAlias(beAuthTable.getString("private_key_alias")); backendAuthConfig.setSSLEnabled(Boolean.TRUE.equals(beAuthTable.getBoolean("is_ssl_enabled"))); - String privateKeyPass = beAuthTable.getString("keystore_password", () -> null); - if (privateKeyPass != null) { - backendAuthConfig.setTruststorePassword(resolveSecret(privateKeyPass)); - } - if (backendAuthConfig.isSSLEnabled()) { - String secretKey = beAuthTable.getString("truststore_password", () -> null); - if (secretKey != null) { - backendAuthConfig.setTruststorePassword(resolveSecret(secretKey)); - } - backendAuthConfig.setSslCertAlias(beAuthTable.getString("ssl_cert_alias")); + String clientSecret = beAuthTable.getString("client_secret", () -> null); + if (clientSecret != null) { + backendAuthConfig.setClient_secret(resolveSecret(clientSecret)); } } return backendAuthConfig; From fc5b878b310e624f0def1e924e5ea0788805a6ad Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Tue, 3 Dec 2024 21:57:17 +0530 Subject: [PATCH 4/9] update readme --- .../readme.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md index c082df1..6bad685 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md @@ -1,5 +1,12 @@ # Backend Authentication for WSO2 API Manager +This module provides a streamlined solution for integrating an external authentication server to securely obtain and +manage access tokens for backend service requests. By default, WSO2 API Manager's gateway forward the end-user's access token +from the invoking client to the backend API. +
This module replaces that default behavior by fetching a new access token +from an external OAuth 2.0-compliant authentication server and replacing the `Authorization` header with the +retrieved token. + ## Configuration Model ```toml @@ -11,3 +18,47 @@ private_key_alias = "wso2carbon" is_ssl_enabled = true ``` +## Key Features + +- **External Auth Server Integration**: Connect to an external authentication server that supports OAuth 2.0 to +retrieve tokens for backend services. +- **Grant Type Support**: Supports the following OAuth 2.0 grant types: + - JWT Bearer Grant + - Client Credentials Grant (In-Progress. Will be available in the next release) +- **Dynamic Header Replacement**: Automatically replaces the existing `Authorization` header in API requests with the +access token retrieved from the configured auth server. + +## Use of Private-Public key pair for PKJWT Auth flow +1. Generate an RSA key pair. Recommended signature algorithm is `RS256`. +2. Upload the private key to the WSO2 API Manager's Key Store. The alias of the uploaded key should be +provided in the configuration(`private_key_alias`). +3. Configure the public key in the external authentication server. +4. If you're enabling SSL, provide the public key of the external authentication server in the +WSO2 API Manager's truststore(`client-truststore.jks`). + + +## Important Notes +### Convert a Private Key from `.pem` to `PKCS#12` Format +```bash +openssl pkcs12 -export \ + -in \ + -inkey \ + -out .p12 \ + -name \ + -passout +``` + +### Import a Private Key in `PKCS#12` format to a `JKS` Key Store +```bash +keytool -importkeystore \ + -srckeystore \ + -srcstoretype PKCS12 \ + -destkeystore \ + -deststoretype JKS \ + -srcstorepass \ + -deststorepass \ + -srcalias \ + -destalias +``` + + From b8da50dbe832d38ee8259489c5c03798ba7ca81d Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Wed, 4 Dec 2024 15:54:28 +0530 Subject: [PATCH 5/9] fix review comments --- distribution/apim-accelerator/bin/merge.sh | 5 +- .../resources/repository/conf/deployment.toml | 13 ++- .../definitions/replaceBackendAuthToken_v1.j2 | 2 +- .../replaceBackendAuthToken_v1.json | 10 +-- .../apim/backendauth/BackendAuthHandler.java | 30 +++++++ .../backendauth/BackendAuthenticator.java | 58 ++++++++----- .../apim/backendauth/Constants.java | 9 ++ .../PrivateKeyJWTBackendAuthenticator.java | 85 ++++++++----------- .../apim/core/config/BackendAuthConfig.java | 19 +++++ .../core/config/OpenHealthcareConfig.java | 65 ++++++++++---- 10 files changed, 202 insertions(+), 94 deletions(-) create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthHandler.java diff --git a/distribution/apim-accelerator/bin/merge.sh b/distribution/apim-accelerator/bin/merge.sh index 7ef2dc2..fc5fac0 100755 --- a/distribution/apim-accelerator/bin/merge.sh +++ b/distribution/apim-accelerator/bin/merge.sh @@ -229,13 +229,14 @@ if [ "${smart_on_fhir_enabled}" == "true" ]; then echo -e "\n#[healthcare.identity.claims]\n#patient_id_claim_uri = \"http://wso2.org/claims/patientId\"\n#patient_id_key = \"patientId\"\n#fhirUser_resource_url_context = \"/r4/Patient\"\n#fhirUser_resource_id_claim_uri = \"http://wso2.org/claims/patientId\"" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null fi - if grep -Fxq "#[healthcare.backend.auth]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml || grep -Fxq "[healthcare.backend.auth]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml + if grep -Fxq "#[[healthcare.backend.auth]]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml || grep -Fxq "[[healthcare.backend.auth]]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml then # code if found echo -e "[WARN] healthcare.backend.auth configuration already exist" else # code if not found - echo -e "\n[healthcare.backend.auth]\ntoken_endpoint = \"https://localhost:9443/oauth2/token\"\nclient_id = \"client_id\"\nclient_secret = \"client_secret\"\nprivate_key_alias = \"wso2carbon\"\nis_ssl_enabled = true" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null + echo -e "\n[[healthcare.backend.auth]]\nname = \"epic_pkjwt\"\nauth_type = \"pkjwt\"\ntoken_endpoint = \"https://localhost:9443/oauth2/token\"\nclient_id = \"client_id\"\nprivate_key_alias = \"wso2carbon\"\nis_ssl_enabled = true + \n#[[healthcare.backend.auth]]\n#name = \"epic_client_credentials\"\n#auth_type = \"client_credentials\"\n#token_endpoint = \"https://localhost:9443/oauth2/token\"\n#client_id = \"client_id\"\n#client_secret = \"client_secret\"\n#is_ssl_enabled = true" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null fi if grep -Fxq "#[healthcare.identity.claim.mgt]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml || grep -Fxq "[healthcare.identity.claim.mgt]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml diff --git a/distribution/apim-accelerator/resources/repository/conf/deployment.toml b/distribution/apim-accelerator/resources/repository/conf/deployment.toml index 4b2a238..5c08d63 100644 --- a/distribution/apim-accelerator/resources/repository/conf/deployment.toml +++ b/distribution/apim-accelerator/resources/repository/conf/deployment.toml @@ -418,13 +418,22 @@ policy.limit.time_unit = "min" patient_id_uri = "http://wso2.org/claims/patientId" patient_id_key = "patientId" -[healthcare.backend.auth] +[[healthcare.backend.auth]] +name = "epic_pkjwt" +auth_type = "pkjwt" token_endpoint = "https://localhost:9443/oauth2/token" client_id = "client_id" -client_secret = "client_secret" private_key_alias = "wso2carbon" is_ssl_enabled = true +#[[healthcare.backend.auth]] +#name = "epic_client_credentials" +#auth_type = "client_credentials" +#token_endpoint = "https://localhost:9443/oauth2/token" +#client_id = "client_id" +#client_secret = "client_secret" +#is_ssl_enabled = true + #[healthcare.deployment.webapps] #name = "Open Healthcare" #name_container_css = "" diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 index 17cc010..9d2708b 100644 --- a/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 @@ -1,3 +1,3 @@ - + diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json index b604b1f..2d70824 100644 --- a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json @@ -15,12 +15,12 @@ ], "policyAttributes": [ { - "name": "authType", - "displayName": "Auth Type", - "description": "Type of Backend Auth Flow. Currently only PKJWT is supported", - "validationRegex": "^(pkjwt|client-credentials)$", + "name": "configName", + "displayName": "Config Name", + "description": "Name of the backend auth server config. Currently only Epic-pkjwt is supported", + "validationRegex": "^(pkjwt|client-credentials|epic_pkjwt)$", "type": "Enum", - "allowedValues" : ["pkjwt"], + "allowedValues" : ["epic_pkjwt"], "required": true } ] diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthHandler.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthHandler.java new file mode 100644 index 0000000..2bad80d --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.healthcare.apim.backendauth; + +import org.apache.synapse.MessageContext; +import org.wso2.healthcare.apim.core.config.BackendAuthConfig; + +/** + * Interface for the backend authentication handler implementations. + */ +public interface BackendAuthHandler { + + String fetchValidAccessToken(MessageContext messageContext, BackendAuthConfig config); +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java index 0e30bad..3dbe267 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java @@ -21,48 +21,49 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.synapse.MessageContext; +import org.apache.synapse.core.axis2.Axis2MessageContext; import org.apache.synapse.mediators.AbstractMediator; import org.wso2.healthcare.apim.core.OpenHealthcareEnvironment; import org.wso2.healthcare.apim.core.OpenHealthcareException; -import org.wso2.healthcare.apim.core.OpenHealthcareRuntimeException; import org.wso2.healthcare.apim.core.config.BackendAuthConfig; +import java.util.Map; + /** * Backend authenticator mediator. */ public class BackendAuthenticator extends AbstractMediator { private static final Log log = LogFactory.getLog(BackendAuthenticator.class); - private String authType; - protected final BackendAuthConfig backendAuthConfig; + private String configName; + private final Map backendAuthConfig; + private final PrivateKeyJWTBackendAuthenticator privateKeyJWTBackendAuthenticator; public BackendAuthenticator() throws OpenHealthcareException { backendAuthConfig = OpenHealthcareEnvironment.getInstance().getConfig().getBackendAuthConfig(); + privateKeyJWTBackendAuthenticator = new PrivateKeyJWTBackendAuthenticator(); + } + @Override public boolean mediate(MessageContext messageContext) { if (log.isDebugEnabled()) { log.debug("Backend authenticator mediator is invoked."); } - if (authType == null) { + BackendAuthConfig config = backendAuthConfig.get(configName); + String accessToken; + + if (configName == null) { log.error("Auth type is not defined in the message context."); return false; } - if (authType.equals(Constants.POLICY_ATTR_AUTH_TYPE_PKJWT)) { + if (config.getAuthType().equals(Constants.POLICY_ATTR_AUTH_TYPE_PKJWT)) { if (log.isDebugEnabled()) { log.debug("Auth type is PKJWT."); } - PrivateKeyJWTBackendAuthenticator privateKeyJWTBackendAuthenticator; - try { - privateKeyJWTBackendAuthenticator = new PrivateKeyJWTBackendAuthenticator(); - } catch (OpenHealthcareException e) { - log.error("Error occurred while initializing the private key JWT backend authenticator.", e); - throw new OpenHealthcareRuntimeException(e); - } - privateKeyJWTBackendAuthenticator.mediate(messageContext); - return true; - } else if (authType.equals(Constants.POLICY_ATTR_AUTH_TYPE_CLIENT_CRED)) { + accessToken = privateKeyJWTBackendAuthenticator.fetchValidAccessToken(messageContext, config); + } else if (config.getAuthType().equals(Constants.POLICY_ATTR_AUTH_TYPE_CLIENT_CRED)) { if (log.isDebugEnabled()) { log.debug("Auth type is CLIENT CREDENTIALS."); } @@ -71,13 +72,32 @@ public boolean mediate(MessageContext messageContext) { log.error("Auth type is not supported."); return false; } + + if (messageContext instanceof Axis2MessageContext) { + org.apache.axis2.context.MessageContext axisMsgCtx = + ((Axis2MessageContext) messageContext).getAxis2MessageContext(); + Object headers = axisMsgCtx.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); + if (headers instanceof Map) { + Map headersMap = (Map) headers; + if (headersMap.get(Constants.HEADER_NAME_AUTHORIZATION) != null) { + headersMap.remove(Constants.HEADER_NAME_AUTHORIZATION); + } + headersMap.put(Constants.HEADER_NAME_AUTHORIZATION, Constants.HEADER_VALUE_BEARER + accessToken); + axisMsgCtx.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headersMap); + } else { + log.warn("Transport headers are not available in the message context."); + } + } else { + log.error("Message context is not an instance of Axis2MessageContext."); + } + return true; } - public String getAuthType() { - return authType; + public String getConfigName() { + return configName; } - public void setAuthType(String authType) { - this.authType = authType; + public void setConfigName(String configName) { + this.configName = configName; } } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java index b4f79ee..ecf1d9b 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java @@ -36,4 +36,13 @@ public class Constants { public static final String POLICY_ATTR_AUTH_TYPE_CLIENT_CRED = "client-credentials"; public static final String POLICY_ATTR_AUTH_TYPE_PKJWT = "pkjwt"; + + public static final String CONFIG_KEYSTORE_PASSWORD = "Security.KeyStore.Password"; + public static final String CONFIG_TRUSTSTORE_PASSWORD = "Security.TrustStore.Password"; + public static final String CLIENT_CREDENTIALS = "client_credentials"; + public static final String NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore"; + public static final String OAUTH2_GRANT_TYPE = "grant_type"; + public static final String OAUTH2_CLIENT_ASSERTION_TYPE = "client_assertion_type"; + public static final String OAUTH2_CLIENT_ASSERTION_TYPE_JWT_BEARER = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; + public static final String OAUTH2_CLIENT_ASSERTION = "client_assertion"; } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java index e829748..b9b5059 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java @@ -37,7 +37,6 @@ import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.apache.synapse.MessageContext; -import org.apache.synapse.core.axis2.Axis2MessageContext; import org.wso2.carbon.core.util.KeyStoreManager; import org.wso2.carbon.utils.CarbonUtils; import org.wso2.healthcare.apim.backendauth.tokenmgt.Token; @@ -56,21 +55,18 @@ import java.security.interfaces.RSAPrivateKey; import java.util.ArrayList; import java.util.Date; -import java.util.Map; import java.util.UUID; /** * Mediator to authenticate with the backend using private key JWT. */ -public class PrivateKeyJWTBackendAuthenticator extends BackendAuthenticator { +public class PrivateKeyJWTBackendAuthenticator implements BackendAuthHandler { private static final Log log = LogFactory.getLog(PrivateKeyJWTBackendAuthenticator.class); private static final JsonParser parser = new JsonParser(); - public PrivateKeyJWTBackendAuthenticator() throws OpenHealthcareException { - } - - public boolean mediate(MessageContext messageContext) { + @Override + public String fetchValidAccessToken(MessageContext messageContext, BackendAuthConfig backendAuthConfig) { log.info("PrivateKeyJWTBackendAuthenticator mediator is started."); @@ -78,9 +74,9 @@ public boolean mediate(MessageContext messageContext) { String tokenEndpoint = backendAuthConfig.getAuthEndpoint(); String keyAlias = backendAuthConfig.getPrivateKeyAlias(); char[] keyStorePass = CarbonUtils.getServerConfiguration().getFirstProperty( - "Security.KeyStore.Password").toCharArray(); + Constants.CONFIG_KEYSTORE_PASSWORD).toCharArray(); char[] trustStorePass = CarbonUtils.getServerConfiguration().getFirstProperty( - "Security.TrustStore.Password").toCharArray(); + Constants.CONFIG_TRUSTSTORE_PASSWORD).toCharArray(); String clientId = backendAuthConfig.getClientId(); int tenantId = Integer.parseInt(messageContext.getProperty(Constants.TENANT_INFO_ID).toString()); KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); @@ -106,53 +102,36 @@ public boolean mediate(MessageContext messageContext) { log.debug("Access token not available in TokenManager."); } try { - token = getAndAddNewToken(clientId, tokenEndpoint, messageContext, privateKey, publicCert, trustStorePass); + token = getAndAddNewToken(messageContext, privateKey, publicCert, trustStorePass, backendAuthConfig); } catch (OpenHealthcareException e) { - log.error("Error occurred while retrieving access token.",e); + log.error("Error occurred while retrieving access token.", e); throw new OpenHealthcareRuntimeException(e); } } accessToken = token.getAccessToken(); - - messageContext.setProperty(Constants.PROPERTY_ACCESS_TOKEN, accessToken); - - if (messageContext instanceof Axis2MessageContext) { - org.apache.axis2.context.MessageContext axisMsgCtx = - ((Axis2MessageContext) messageContext).getAxis2MessageContext(); - Object headers = axisMsgCtx.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); - if (headers instanceof Map) { - Map headersMap = (Map) headers; - if (headersMap.get(Constants.HEADER_NAME_AUTHORIZATION) != null) { - headersMap.remove(Constants.HEADER_NAME_AUTHORIZATION); - } - headersMap.put(Constants.HEADER_NAME_AUTHORIZATION, Constants.HEADER_VALUE_BEARER + accessToken); - axisMsgCtx.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headersMap); - }else { - log.warn("Transport headers are not available in the message context."); - } - }else { - log.error("Message context is not an instance of Axis2MessageContext."); - } - return true; + return accessToken; } /** * Retrieve token from the token store. If token is not available or inactive, generate new token. - * @param clientId - to retrieve token - * @param tokenEndpoint - to retrieve token + * * @param messageContext - to set error response - * @param privateKey - to sign the JWT + * @param privateKey - to sign the JWT * @return Token * @throws OpenHealthcareException - when error occurred while retrieving token */ - private synchronized Token getAndAddNewToken(String clientId, String tokenEndpoint, MessageContext messageContext, Key privateKey, Certificate publicCert, char[] trustStorePass) throws OpenHealthcareException { + private synchronized Token getAndAddNewToken(MessageContext messageContext, Key privateKey, Certificate publicCert, + char[] trustStorePass, BackendAuthConfig config) + throws OpenHealthcareException { + String clientId = config.getClientId(); + String tokenEndpoint = config.getAuthEndpoint(); Token token = TokenManager.getToken(clientId, tokenEndpoint); if (token == null || !token.isActive()) { String jwt = generateJWT(clientId, tokenEndpoint, privateKey, publicCert); - token = getAccessToken(messageContext, tokenEndpoint, jwt, backendAuthConfig.isSSLEnabled(), trustStorePass); + token = getAccessToken(messageContext, tokenEndpoint, jwt, config.isSSLEnabled(), trustStorePass); TokenManager.addToken(clientId, tokenEndpoint, token); - }else { + } else { if (log.isDebugEnabled()) { log.debug("Token exists in the token store."); } @@ -162,13 +141,15 @@ private synchronized Token getAndAddNewToken(String clientId, String tokenEndpoi /** * Generate JWT token with signature. - * @param clientId - to add as subject + * + * @param clientId - to add as subject * @param tokenEndpoint - audience - * @param privateKey - to sign the JWT + * @param privateKey - to sign the JWT * @return JWT as string * @throws OpenHealthcareException JOSEException */ - private String generateJWT(String clientId, String tokenEndpoint, Key privateKey, Certificate publicCert) throws OpenHealthcareException { + private String generateJWT(String clientId, String tokenEndpoint, Key privateKey, Certificate publicCert) throws + OpenHealthcareException { try { RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey; @@ -204,10 +185,11 @@ private String generateJWT(String clientId, String tokenEndpoint, Key privateKey /** * Retrieve access token from the token endpoint. This makes external HTTP call to the token endpoint. + * * @param messageContext - to set error response - * @param tokenEndpoint - to retrieve access token - * @param jwt - signed JWT including claims - * @param isSSLEnabled - whether SSL is enabled + * @param tokenEndpoint - to retrieve access token + * @param jwt - signed JWT including claims + * @param isSSLEnabled - whether SSL is enabled * @return Token * @throws OpenHealthcareException - when error occurred while retrieving access token */ @@ -220,12 +202,13 @@ private static Token getAccessToken( throws OpenHealthcareException { long curTimeInMillis = System.currentTimeMillis(); - String trustStorePath = System.getProperty("javax.net.ssl.trustStore"); + String trustStorePath = System.getProperty(Constants.NET_SSL_TRUST_STORE); HttpPost postRequest = new HttpPost(tokenEndpoint); ArrayList parameters = new ArrayList<>(); - parameters.add(new BasicNameValuePair("grant_type", "client_credentials")); - parameters.add(new BasicNameValuePair("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")); - parameters.add(new BasicNameValuePair("client_assertion", jwt)); + parameters.add(new BasicNameValuePair(Constants.OAUTH2_GRANT_TYPE, Constants.CLIENT_CREDENTIALS)); + parameters.add(new BasicNameValuePair( + Constants.OAUTH2_CLIENT_ASSERTION_TYPE, Constants.OAUTH2_CLIENT_ASSERTION_TYPE_JWT_BEARER)); + parameters.add(new BasicNameValuePair(Constants.OAUTH2_CLIENT_ASSERTION, jwt)); try { postRequest.setEntity(new UrlEncodedFormEntity(parameters)); @@ -267,9 +250,10 @@ private static Token getAccessToken( /** * Handle error response from the token endpoint. + * * @param messageContext - to set error response * @param responseStatus - HTTP status code - * @param respMessage - response message + * @param respMessage - response message * @throws OpenHealthcareException - to propagate the error */ private static void handleTokenRequestError(MessageContext messageContext, int responseStatus, String respMessage) throws OpenHealthcareException { @@ -282,7 +266,8 @@ private static void handleTokenRequestError(MessageContext messageContext, int r /** * Extract token from the response message. - * @param respMessage - response message + * + * @param respMessage - response message * @param curTimeInMillis - current time in milliseconds * @return - Token */ diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java index b373a09..c6224ac 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java @@ -23,12 +23,15 @@ */ public class BackendAuthConfig { + private String name; private String authEndpoint; private String clientId; private char[] client_secret; private boolean isSSLEnabled; private String privateKeyAlias; + private String authType; + public String getAuthEndpoint() { return authEndpoint; } @@ -68,4 +71,20 @@ public String getPrivateKeyAlias() { public void setPrivateKeyAlias(String privateKeyAlias) { this.privateKeyAlias = privateKeyAlias; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAuthType() { + return authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java index 94bff92..9fcb0b1 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java @@ -71,7 +71,7 @@ public class OpenHealthcareConfig { private Map notificationMailConfig; private OrganizationConfig organizationConfig; private ScopeMgtConfig scopeMgtConfig; - private BackendAuthConfig backendAuthConfig; + private Map backendAuthConfig; private OpenHealthcareConfig(TomlParseResult config) throws OpenHealthcareException { this.config = config; @@ -154,7 +154,7 @@ public ScopeMgtConfig getScopeMgtConfig() { return scopeMgtConfig; } - public BackendAuthConfig getBackendAuthConfig() { + public Map getBackendAuthConfig() { return backendAuthConfig; } @@ -579,21 +579,56 @@ private ScopeMgtConfig buildScopeMgtConfig() { } - private BackendAuthConfig buildBackendAuthConfig() { - BackendAuthConfig backendAuthConfig = new BackendAuthConfig(); - Object backendAuthObj = config.get("healthcare.backend.auth"); - if (backendAuthObj instanceof TomlTable) { - TomlTable beAuthTable = (TomlTable) backendAuthObj; - backendAuthConfig.setAuthEndpoint(beAuthTable.getString("token_endpoint")); - backendAuthConfig.setClientId(beAuthTable.getString("client_id")); - backendAuthConfig.setPrivateKeyAlias(beAuthTable.getString("private_key_alias")); - backendAuthConfig.setSSLEnabled(Boolean.TRUE.equals(beAuthTable.getBoolean("is_ssl_enabled"))); - String clientSecret = beAuthTable.getString("client_secret", () -> null); - if (clientSecret != null) { - backendAuthConfig.setClient_secret(resolveSecret(clientSecret)); + private Map buildBackendAuthConfig() throws OpenHealthcareException { +// BackendAuthConfig backendAuthConfig = new BackendAuthConfig(); +// Object backendAuthObj = config.get("healthcare.backend.auth"); +// if (backendAuthObj instanceof TomlTable) { +// TomlTable beAuthTable = (TomlTable) backendAuthObj; +// backendAuthConfig.setAuthEndpoint(beAuthTable.getString("token_endpoint")); +// backendAuthConfig.setClientId(beAuthTable.getString("client_id")); +// backendAuthConfig.setPrivateKeyAlias(beAuthTable.getString("private_key_alias")); +// backendAuthConfig.setSSLEnabled(Boolean.TRUE.equals(beAuthTable.getBoolean("is_ssl_enabled"))); +// String clientSecret = beAuthTable.getString("client_secret", () -> null); +// if (clientSecret != null) { +// backendAuthConfig.setClient_secret(resolveSecret(clientSecret)); +// } +// } +// return backendAuthConfig; + + Map backendAuthConfigs = new HashMap<>(); + Object backendAuthConfigObject = config.get("healthcare.backend.auth"); + if (backendAuthConfigObject instanceof TomlArray) { + TomlArray authConfig = (TomlArray) backendAuthConfigObject; + List authConfigList = authConfig.toList(); + for (Object notification : authConfigList) { + if (notification instanceof TomlTable) { + TomlTable beAuthTable = (TomlTable) notification; + BackendAuthConfig backendAuthConfig = new BackendAuthConfig(); + if (StringUtils.isEmpty(beAuthTable.getString("name")) || + StringUtils.isEmpty(beAuthTable.getString("token_endpoint")) || + StringUtils.isEmpty(beAuthTable.getString("auth_type")) || + StringUtils.isEmpty(beAuthTable.getString("client_id"))) { + throw new OpenHealthcareException("One or more mandatory parameter/s in the notification " + + "config missing. [Mandatory params - name, token_endpoint, client_id]"); + } + backendAuthConfig.setName(beAuthTable.getString("name")); + backendAuthConfig.setAuthEndpoint(beAuthTable.getString("token_endpoint")); + backendAuthConfig.setClientId(beAuthTable.getString("client_id")); + String keyAlias = beAuthTable.getString("private_key_alias", () -> null); + if (keyAlias != null) { + backendAuthConfig.setPrivateKeyAlias(keyAlias); + } + backendAuthConfig.setSSLEnabled(Boolean.TRUE.equals(beAuthTable.getBoolean("is_ssl_enabled"))); + String clientSecret = beAuthTable.getString("client_secret", () -> null); + if (clientSecret != null) { + backendAuthConfig.setClient_secret(resolveSecret(clientSecret)); + } + backendAuthConfig.setAuthType(beAuthTable.getString("auth_type")); + backendAuthConfigs.put(backendAuthConfig.getName(), backendAuthConfig); + } } } - return backendAuthConfig; + return backendAuthConfigs; } private char[] resolveSecret(String secret) { From daa4bf5708f8a5dc50a17198b91497494bcd663e Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Thu, 5 Dec 2024 09:33:19 +0530 Subject: [PATCH 6/9] add comments to configs --- distribution/apim-accelerator/bin/merge.sh | 2 +- .../resources/repository/conf/deployment.toml | 6 +++++- .../specifications/replaceBackendAuthToken_v1.json | 6 ++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/distribution/apim-accelerator/bin/merge.sh b/distribution/apim-accelerator/bin/merge.sh index fc5fac0..106b0c0 100755 --- a/distribution/apim-accelerator/bin/merge.sh +++ b/distribution/apim-accelerator/bin/merge.sh @@ -235,7 +235,7 @@ if [ "${smart_on_fhir_enabled}" == "true" ]; then echo -e "[WARN] healthcare.backend.auth configuration already exist" else # code if not found - echo -e "\n[[healthcare.backend.auth]]\nname = \"epic_pkjwt\"\nauth_type = \"pkjwt\"\ntoken_endpoint = \"https://localhost:9443/oauth2/token\"\nclient_id = \"client_id\"\nprivate_key_alias = \"wso2carbon\"\nis_ssl_enabled = true + echo -e "\n[[healthcare.backend.auth]]\n# Name of the authentication method. This name must be matched with the Config Name policy attribute in the -\n# - Replace Backend Auth Token policy.\nname = \"epic_pkjwt\"\n# Authentication type. Only pkjwt is supported atm.\n\nauth_type = \"pkjwt\"\n# External Auth server's Token endpoint URL.\ntoken_endpoint = \"https://localhost:9443/oauth2/token\"\nclient_id = \"client_id\"\nprivate_key_alias = \"key_alias\"\nis_ssl_enabled = true \n#[[healthcare.backend.auth]]\n#name = \"epic_client_credentials\"\n#auth_type = \"client_credentials\"\n#token_endpoint = \"https://localhost:9443/oauth2/token\"\n#client_id = \"client_id\"\n#client_secret = \"client_secret\"\n#is_ssl_enabled = true" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null fi diff --git a/distribution/apim-accelerator/resources/repository/conf/deployment.toml b/distribution/apim-accelerator/resources/repository/conf/deployment.toml index 5c08d63..1ed6a52 100644 --- a/distribution/apim-accelerator/resources/repository/conf/deployment.toml +++ b/distribution/apim-accelerator/resources/repository/conf/deployment.toml @@ -419,11 +419,15 @@ patient_id_uri = "http://wso2.org/claims/patientId" patient_id_key = "patientId" [[healthcare.backend.auth]] +# Name of the authentication method. This name must be matched with the Config Name policy attribute in the - +# - Replace Backend Auth Token policy. name = "epic_pkjwt" +# Authentication type. Only pkjwt is supported atm. auth_type = "pkjwt" +# External Auth server's Token endpoint URL. token_endpoint = "https://localhost:9443/oauth2/token" client_id = "client_id" -private_key_alias = "wso2carbon" +private_key_alias = "key_alias" is_ssl_enabled = true #[[healthcare.backend.auth]] diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json index 2d70824..9aacf4c 100644 --- a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json @@ -17,10 +17,8 @@ { "name": "configName", "displayName": "Config Name", - "description": "Name of the backend auth server config. Currently only Epic-pkjwt is supported", - "validationRegex": "^(pkjwt|client-credentials|epic_pkjwt)$", - "type": "Enum", - "allowedValues" : ["epic_pkjwt"], + "description": "Name of the backend auth server config. This has to an exact match with the config name in the deployment.toml [[healthcare.backend.auth]] section", + "type": "String", "required": true } ] From 2baf7e5a6442a5828c556972e262416fefba19b3 Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Thu, 5 Dec 2024 14:19:36 +0530 Subject: [PATCH 7/9] address review comments --- distribution/apim-accelerator/bin/merge.sh | 4 +- .../resources/repository/conf/deployment.toml | 6 +- .../definitions/replaceBackendAuthToken_v1.j2 | 2 +- .../replaceBackendAuthToken_v1.json | 4 +- pom.xml | 6 + .../pom.xml | 1 - .../readme.md | 22 +++- .../backendauth/BackendAuthenticator.java | 39 ++++--- .../apim/backendauth/Constants.java | 1 + .../healthcare/apim/backendauth/Utils.java | 94 +++++++++++++++ .../{ => impl}/BackendAuthHandler.java | 15 ++- ...ClientCredentialsBackendAuthenticator.java | 107 ++++++++++++++++++ .../PrivateKeyJWTBackendAuthenticator.java | 100 +++------------- .../tokenmgt/InMemoryTokenStore.java | 3 + .../apim/backendauth/tokenmgt/Token.java | 3 + .../backendauth/tokenmgt/TokenManager.java | 3 + .../apim/core/config/BackendAuthConfig.java | 13 +-- .../core/config/OpenHealthcareConfig.java | 17 +-- 18 files changed, 296 insertions(+), 144 deletions(-) rename product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/{ => impl}/BackendAuthHandler.java (71%) create mode 100644 product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java rename product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/{ => impl}/PrivateKeyJWTBackendAuthenticator.java (69%) diff --git a/distribution/apim-accelerator/bin/merge.sh b/distribution/apim-accelerator/bin/merge.sh index 106b0c0..f3a00e1 100755 --- a/distribution/apim-accelerator/bin/merge.sh +++ b/distribution/apim-accelerator/bin/merge.sh @@ -235,8 +235,8 @@ if [ "${smart_on_fhir_enabled}" == "true" ]; then echo -e "[WARN] healthcare.backend.auth configuration already exist" else # code if not found - echo -e "\n[[healthcare.backend.auth]]\n# Name of the authentication method. This name must be matched with the Config Name policy attribute in the -\n# - Replace Backend Auth Token policy.\nname = \"epic_pkjwt\"\n# Authentication type. Only pkjwt is supported atm.\n\nauth_type = \"pkjwt\"\n# External Auth server's Token endpoint URL.\ntoken_endpoint = \"https://localhost:9443/oauth2/token\"\nclient_id = \"client_id\"\nprivate_key_alias = \"key_alias\"\nis_ssl_enabled = true - \n#[[healthcare.backend.auth]]\n#name = \"epic_client_credentials\"\n#auth_type = \"client_credentials\"\n#token_endpoint = \"https://localhost:9443/oauth2/token\"\n#client_id = \"client_id\"\n#client_secret = \"client_secret\"\n#is_ssl_enabled = true" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null + echo -e "\n#[[healthcare.backend.auth]]\n## Name of the authentication method. This name must be matched with the Config Name policy attribute in the -\n## - Replace Backend Auth Token policy.\n#name = \"epic_pkjwt\"\n## Authentication type. Only pkjwt and client_credentials are supported atm.\n#auth_type = \"pkjwt\"\n## External Auth server's Token endpoint URL.\n#token_endpoint = \"https://localhost:9443/oauth2/token\"\n#client_id = \"client_id\"\n#private_key_alias = \"key_alias\" + \n#[[healthcare.backend.auth]]\n#name = \"epic_client_credentials\"\n#auth_type = \"client_credentials\"\n#token_endpoint = \"https://localhost:9443/oauth2/token\"\n#client_id = \"client_id\"\n#client_secret = \"client_secret\"" | tee -a "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml >/dev/null fi if grep -Fxq "#[healthcare.identity.claim.mgt]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml || grep -Fxq "[healthcare.identity.claim.mgt]" "${WSO2_OH_APIM_HOME}"/repository/conf/deployment.toml diff --git a/distribution/apim-accelerator/resources/repository/conf/deployment.toml b/distribution/apim-accelerator/resources/repository/conf/deployment.toml index 1ed6a52..a4e1b31 100644 --- a/distribution/apim-accelerator/resources/repository/conf/deployment.toml +++ b/distribution/apim-accelerator/resources/repository/conf/deployment.toml @@ -419,16 +419,15 @@ patient_id_uri = "http://wso2.org/claims/patientId" patient_id_key = "patientId" [[healthcare.backend.auth]] -# Name of the authentication method. This name must be matched with the Config Name policy attribute in the - +# Name of the authentication method. This name must be matched with the Backend Auth Config policy attribute in the - # - Replace Backend Auth Token policy. name = "epic_pkjwt" -# Authentication type. Only pkjwt is supported atm. +# Authentication type. Only pkjwt and client_credentials are supported atm. auth_type = "pkjwt" # External Auth server's Token endpoint URL. token_endpoint = "https://localhost:9443/oauth2/token" client_id = "client_id" private_key_alias = "key_alias" -is_ssl_enabled = true #[[healthcare.backend.auth]] #name = "epic_client_credentials" @@ -436,7 +435,6 @@ is_ssl_enabled = true #token_endpoint = "https://localhost:9443/oauth2/token" #client_id = "client_id" #client_secret = "client_secret" -#is_ssl_enabled = true #[healthcare.deployment.webapps] #name = "Open Healthcare" diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 index 9d2708b..18117ad 100644 --- a/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/definitions/replaceBackendAuthToken_v1.j2 @@ -1,3 +1,3 @@ - + diff --git a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json index 9aacf4c..da07d63 100644 --- a/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json +++ b/distribution/apim-accelerator/resources/repository/resources/operation_policies/specifications/replaceBackendAuthToken_v1.json @@ -15,8 +15,8 @@ ], "policyAttributes": [ { - "name": "configName", - "displayName": "Config Name", + "name": "backendAuthConfig", + "displayName": "Backend Auth Config", "description": "Name of the backend auth server config. This has to an exact match with the config name in the deployment.toml [[healthcare.backend.auth]] section", "type": "String", "required": true diff --git a/pom.xml b/pom.xml index 63d2b32..6795f10 100644 --- a/pom.xml +++ b/pom.xml @@ -494,6 +494,12 @@ ${carbon.kernel.version} test + + + + commons-logging + commons-logging + diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml index c2f9021..990e2ba 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/pom.xml @@ -67,7 +67,6 @@ org.apache.felix maven-bundle-plugin true - 5.1.1 ${project.artifactId}.${project.version} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md index 6bad685..92c98d9 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md @@ -10,13 +10,25 @@ retrieved token. ## Configuration Model ```toml -[healthcare.backend.auth] +[[healthcare.backend.auth]] +name = "backend-auth" +auth_type = "pkjwt" or "client-credentials" token_endpoint = "https://localhost:9443/oauth2/token" client_id = "client_id" -client_secret = "client_secret" -private_key_alias = "wso2carbon" -is_ssl_enabled = true +client_secret = "client_secret" # Only for Client Credentials flow +private_key_alias = "key_alias" # Only for PKJWT flow ``` +### Parameters + +| Parameter | Description | Example Value | +|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| +| `name` | Name of the authentication method. This name must be matched with the `Backend Auth Config` policy attribute in the `Replace Backend Auth Token` policy. | `epic-pkjwt` | +| `auth_type` | Authentication type. Only `pkjwt` and `client_credentials` are supported atm. | `pkjwt` | +| `token_endpoint` | The URL of the external auth server's token endpoint for obtaining tokens. | `https://external.auth.com:9443/oauth2/token` | +| `client_id` | The client identifier registered with the auth server. | `client_id` | +| `client_secret` | The client secret associated with the client ID (if applicable). | `client_secret` | +| `private_key_alias` | The alias of the private key used for signing JWTs (if applicable). This key must be added to the Primary Keystore | `key_alias` | + ## Key Features @@ -24,7 +36,7 @@ is_ssl_enabled = true retrieve tokens for backend services. - **Grant Type Support**: Supports the following OAuth 2.0 grant types: - JWT Bearer Grant - - Client Credentials Grant (In-Progress. Will be available in the next release) + - Client Credentials Grant - **Dynamic Header Replacement**: Automatically replaces the existing `Authorization` header in API requests with the access token retrieved from the configured auth server. diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java index 3dbe267..a59a826 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java @@ -23,6 +23,9 @@ import org.apache.synapse.MessageContext; import org.apache.synapse.core.axis2.Axis2MessageContext; import org.apache.synapse.mediators.AbstractMediator; +import org.wso2.healthcare.apim.backendauth.impl.BackendAuthHandler; +import org.wso2.healthcare.apim.backendauth.impl.ClientCredentialsBackendAuthenticator; +import org.wso2.healthcare.apim.backendauth.impl.PrivateKeyJWTBackendAuthenticator; import org.wso2.healthcare.apim.core.OpenHealthcareEnvironment; import org.wso2.healthcare.apim.core.OpenHealthcareException; import org.wso2.healthcare.apim.core.config.BackendAuthConfig; @@ -38,10 +41,12 @@ public class BackendAuthenticator extends AbstractMediator { private String configName; private final Map backendAuthConfig; private final PrivateKeyJWTBackendAuthenticator privateKeyJWTBackendAuthenticator; + private final ClientCredentialsBackendAuthenticator clientCredentialsBackendAuthenticator; public BackendAuthenticator() throws OpenHealthcareException { backendAuthConfig = OpenHealthcareEnvironment.getInstance().getConfig().getBackendAuthConfig(); privateKeyJWTBackendAuthenticator = new PrivateKeyJWTBackendAuthenticator(); + clientCredentialsBackendAuthenticator = new ClientCredentialsBackendAuthenticator(); } @@ -53,24 +58,30 @@ public boolean mediate(MessageContext messageContext) { BackendAuthConfig config = backendAuthConfig.get(configName); String accessToken; + BackendAuthHandler currentAuthenticator; if (configName == null) { log.error("Auth type is not defined in the message context."); return false; } - if (config.getAuthType().equals(Constants.POLICY_ATTR_AUTH_TYPE_PKJWT)) { - if (log.isDebugEnabled()) { - log.debug("Auth type is PKJWT."); - } - accessToken = privateKeyJWTBackendAuthenticator.fetchValidAccessToken(messageContext, config); - } else if (config.getAuthType().equals(Constants.POLICY_ATTR_AUTH_TYPE_CLIENT_CRED)) { - if (log.isDebugEnabled()) { - log.debug("Auth type is CLIENT CREDENTIALS."); - } - return true; - } else { - log.error("Auth type is not supported."); - return false; + switch (config.getAuthType()) { + case Constants.POLICY_ATTR_AUTH_TYPE_PKJWT: + if (log.isDebugEnabled()) { + log.debug("Auth type is PKJWT."); + } + currentAuthenticator = privateKeyJWTBackendAuthenticator; + accessToken = privateKeyJWTBackendAuthenticator.fetchValidAccessToken(messageContext, config); + break; + case Constants.POLICY_ATTR_AUTH_TYPE_CLIENT_CRED: + if (log.isDebugEnabled()) { + log.debug("Auth type is CLIENT CREDENTIALS."); + } + currentAuthenticator = clientCredentialsBackendAuthenticator; + accessToken = clientCredentialsBackendAuthenticator.fetchValidAccessToken(messageContext, config); + break; + default: + log.error("Auth type is not supported."); + return false; } if (messageContext instanceof Axis2MessageContext) { @@ -82,7 +93,7 @@ public boolean mediate(MessageContext messageContext) { if (headersMap.get(Constants.HEADER_NAME_AUTHORIZATION) != null) { headersMap.remove(Constants.HEADER_NAME_AUTHORIZATION); } - headersMap.put(Constants.HEADER_NAME_AUTHORIZATION, Constants.HEADER_VALUE_BEARER + accessToken); + headersMap.put(Constants.HEADER_NAME_AUTHORIZATION, currentAuthenticator.getAuthHeaderScheme() + accessToken); axisMsgCtx.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headersMap); } else { log.warn("Transport headers are not available in the message context."); diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java index ecf1d9b..243a5ae 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java @@ -33,6 +33,7 @@ public class Constants { public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; public static final String HEADER_NAME_AUTHORIZATION = "Authorization"; public static final String HEADER_VALUE_BEARER = "Bearer "; + public static final String HEADER_VALUE_BASIC = "Basic "; public static final String POLICY_ATTR_AUTH_TYPE_CLIENT_CRED = "client-credentials"; public static final String POLICY_ATTR_AUTH_TYPE_PKJWT = "pkjwt"; diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java index ea0d91a..181ae6e 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java @@ -18,8 +18,16 @@ package org.wso2.healthcare.apim.backendauth; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; @@ -33,14 +41,20 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContexts; import org.apache.http.ssl.TrustStrategy; +import org.apache.http.util.EntityUtils; import org.apache.synapse.MessageContext; import org.apache.synapse.SynapseConstants; import org.apache.synapse.core.axis2.Axis2MessageContext; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.healthcare.apim.backendauth.tokenmgt.Token; import org.wso2.healthcare.apim.core.OpenHealthcareException; +import org.wso2.healthcare.apim.core.OpenHealthcareRuntimeException; import javax.net.ssl.SSLContext; import java.io.FileInputStream; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.KeyStore; @@ -48,6 +62,8 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; /** * Utility class for the backend authentication. @@ -61,6 +77,8 @@ public class Utils { public static final String HTTPS_PROTOCOL = "https"; private static final String[] SUPPORTED_HTTP_PROTOCOLS = {"TLSv1.2"}; + private static final JsonParser parser = new JsonParser(); + public static void setErrorResponse(MessageContext messageContext, Throwable e, int errorCode, String msg) { messageContext.setProperty(SynapseConstants.ERROR_CODE, errorCode); @@ -200,4 +218,80 @@ public static String getKID(X509Certificate cert) { return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(kid.getBytes(StandardCharsets.UTF_8)); } + public static Token initiateTokenRequest(MessageContext messageContext, HttpPost postRequest, + List parameters) + throws OpenHealthcareException { + long curTimeInMillis = System.currentTimeMillis(); + String trustStorePath = System.getProperty(Constants.NET_SSL_TRUST_STORE); + char[] trustStorePass = CarbonUtils.getServerConfiguration().getFirstProperty( + Constants.CONFIG_TRUSTSTORE_PASSWORD).toCharArray(); + + try { + postRequest.setEntity(new UrlEncodedFormEntity(parameters)); + } catch (UnsupportedEncodingException e) { + String message = "Error occurred while preparing access token request payload."; + log.error(message); + throw new OpenHealthcareException(message, e); + } + CloseableHttpClient httpsClient = Utils.getHttpsClient(trustStorePath, trustStorePass); + CloseableHttpResponse response; + try { + response = httpsClient.execute(postRequest); + HttpEntity responseEntity = response.getEntity(); + if (responseEntity == null) { + String message = "Failed to retrieve access token : No entity received."; + log.error(message); + throw new OpenHealthcareException(message); + } + int responseStatus = response.getStatusLine().getStatusCode(); + String respMessage; + respMessage = EntityUtils.toString(responseEntity); + + if (responseStatus == HttpURLConnection.HTTP_OK) { + return extractToken(respMessage, curTimeInMillis); + } else { + handleTokenRequestError(messageContext, responseStatus, respMessage); + } + } catch (IOException e) { + throw new OpenHealthcareRuntimeException(e); + } + throw new OpenHealthcareException("Error occurred while retrieving access token."); + } + + /** + * Extract token from the response message. + * + * @param respMessage - response message + * @param curTimeInMillis - current time in milliseconds + * @return - Token + */ + private static Token extractToken(String respMessage, long curTimeInMillis) { + JsonElement jsonElement = parser.parse(respMessage); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + String accessToken = jsonObject.get("access_token").getAsString(); + long expireIn = jsonObject.get("expires_in").getAsLong(); + + Token token = new Token(accessToken, curTimeInMillis, expireIn * 1000); + if (log.isDebugEnabled()) { + log.debug(token); + } + return token; + } + + /** + * Handle error response from the token endpoint. + * + * @param messageContext - to set error response + * @param responseStatus - HTTP status code + * @param respMessage - response message + * @throws OpenHealthcareException - to propagate the error + */ + private static void handleTokenRequestError(MessageContext messageContext, int responseStatus, String respMessage) throws OpenHealthcareException { + String message = "Error occurred while retrieving access token. Response: [Status : " + responseStatus + " Message: " + respMessage + "]"; + log.error(message); + OpenHealthcareException exp = new OpenHealthcareException(message); + Utils.setErrorResponse(messageContext, exp, responseStatus, message); + throw exp; + } + } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthHandler.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java similarity index 71% rename from product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthHandler.java rename to product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java index 2bad80d..3afd56b 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthHandler.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java @@ -16,7 +16,7 @@ * under the License. */ -package org.wso2.healthcare.apim.backendauth; +package org.wso2.healthcare.apim.backendauth.impl; import org.apache.synapse.MessageContext; import org.wso2.healthcare.apim.core.config.BackendAuthConfig; @@ -26,5 +26,16 @@ */ public interface BackendAuthHandler { - String fetchValidAccessToken(MessageContext messageContext, BackendAuthConfig config); + /** + * Fetch a valid access token from the relevant Auth implementation. + * + * @param messageContext Message context. + * @return Valid access token. + */ + String fetchValidAccessToken(MessageContext messageContext, BackendAuthConfig backendAuthConfig); + + /** + * Get the auth header scheme. i.e. Bearer, Basic etc. + */ + String getAuthHeaderScheme(); } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java new file mode 100644 index 0000000..e021454 --- /dev/null +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java @@ -0,0 +1,107 @@ +package org.wso2.healthcare.apim.backendauth.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.NameValuePair; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.message.BasicNameValuePair; +import org.apache.synapse.MessageContext; +import org.wso2.healthcare.apim.backendauth.Constants; +import org.wso2.healthcare.apim.backendauth.Utils; +import org.wso2.healthcare.apim.backendauth.tokenmgt.Token; +import org.wso2.healthcare.apim.backendauth.tokenmgt.TokenManager; +import org.wso2.healthcare.apim.core.OpenHealthcareException; +import org.wso2.healthcare.apim.core.OpenHealthcareRuntimeException; +import org.wso2.healthcare.apim.core.config.BackendAuthConfig; + +import java.util.ArrayList; + +/** + * Mediator to authenticate with the backend using client credentials. + */ +public class ClientCredentialsBackendAuthenticator implements BackendAuthHandler{ + + private static final Log log = LogFactory.getLog(ClientCredentialsBackendAuthenticator.class); + @Override + public String fetchValidAccessToken(MessageContext messageContext, BackendAuthConfig backendAuthConfig) { + log.info("PrivateKeyJWTBackendAuthenticator mediator is started."); + + String accessToken; + String tokenEndpoint = backendAuthConfig.getAuthEndpoint(); + String clientId = backendAuthConfig.getClientId(); + + if (log.isDebugEnabled()) { + log.debug("Configured client ID: " + clientId); + } + + Token token = TokenManager.getToken(clientId, tokenEndpoint); + if (token == null || !token.isActive()) { + if (log.isDebugEnabled()) { + log.debug("Access token not available in TokenManager."); + } + try { + token = getAndAddNewToken(messageContext, backendAuthConfig); + } catch (OpenHealthcareException e) { + log.error("Error occurred while retrieving access token.", e); + throw new OpenHealthcareRuntimeException(e); + } + } + accessToken = token.getAccessToken(); + return accessToken; + } + + @Override + public String getAuthHeaderScheme() { + return Constants.HEADER_VALUE_BEARER; + } + + /** + * Get and add a new token to the token store. + * + * @param messageContext Message context. + * @param config Backend auth configuration. + * @return Token. + * @throws OpenHealthcareException If an error occurs while fetching the token. + */ + private synchronized Token getAndAddNewToken(MessageContext messageContext, + BackendAuthConfig config) throws OpenHealthcareException { + + String clientId = config.getClientId(); + String tokenEndpoint = config.getAuthEndpoint(); + Token token = TokenManager.getToken(clientId, tokenEndpoint); + if (token == null || !token.isActive()) { + token = getAccessToken(messageContext, tokenEndpoint, config); + TokenManager.addToken(clientId, tokenEndpoint, token); + } else { + if (log.isDebugEnabled()) { + log.debug("Token exists in the token store."); + } + } + return token; + } + + /** + * Get the access token from the token endpoint. + * + * @param messageContext Message context. + * @param tokenEndpoint Token endpoint. + * @param config Backend auth configuration. + * @return Access token. + * @throws OpenHealthcareException If an error occurs while fetching the token. + */ + private static Token getAccessToken( + MessageContext messageContext, + String tokenEndpoint, + BackendAuthConfig config) + throws OpenHealthcareException { + + HttpPost postRequest = new HttpPost(tokenEndpoint); + ArrayList parameters = new ArrayList<>(); + parameters.add(new BasicNameValuePair(Constants.OAUTH2_GRANT_TYPE, Constants.CLIENT_CREDENTIALS)); + parameters.add(new BasicNameValuePair( + "client_id", config.getClientId())); + parameters.add(new BasicNameValuePair( + "client_secret", String.valueOf(config.getClientSecret()))); + return Utils.initiateTokenRequest(messageContext, postRequest, parameters); + } +} diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/PrivateKeyJWTBackendAuthenticator.java similarity index 69% rename from product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java rename to product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/PrivateKeyJWTBackendAuthenticator.java index b9b5059..0c50a29 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/PrivateKeyJWTBackendAuthenticator.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/PrivateKeyJWTBackendAuthenticator.java @@ -16,7 +16,7 @@ * under the License. */ -package org.wso2.healthcare.apim.backendauth; +package org.wso2.healthcare.apim.backendauth.impl; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -33,12 +33,14 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.apache.synapse.MessageContext; +import org.jetbrains.annotations.NotNull; import org.wso2.carbon.core.util.KeyStoreManager; import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.healthcare.apim.backendauth.Constants; +import org.wso2.healthcare.apim.backendauth.Utils; import org.wso2.healthcare.apim.backendauth.tokenmgt.Token; import org.wso2.healthcare.apim.backendauth.tokenmgt.TokenManager; import org.wso2.healthcare.apim.core.OpenHealthcareException; @@ -63,7 +65,11 @@ public class PrivateKeyJWTBackendAuthenticator implements BackendAuthHandler { private static final Log log = LogFactory.getLog(PrivateKeyJWTBackendAuthenticator.class); - private static final JsonParser parser = new JsonParser(); + + @Override + public String getAuthHeaderScheme() { + return Constants.HEADER_VALUE_BEARER; + } @Override public String fetchValidAccessToken(MessageContext messageContext, BackendAuthConfig backendAuthConfig) { @@ -75,8 +81,6 @@ public String fetchValidAccessToken(MessageContext messageContext, BackendAuthCo String keyAlias = backendAuthConfig.getPrivateKeyAlias(); char[] keyStorePass = CarbonUtils.getServerConfiguration().getFirstProperty( Constants.CONFIG_KEYSTORE_PASSWORD).toCharArray(); - char[] trustStorePass = CarbonUtils.getServerConfiguration().getFirstProperty( - Constants.CONFIG_TRUSTSTORE_PASSWORD).toCharArray(); String clientId = backendAuthConfig.getClientId(); int tenantId = Integer.parseInt(messageContext.getProperty(Constants.TENANT_INFO_ID).toString()); KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); @@ -102,7 +106,7 @@ public String fetchValidAccessToken(MessageContext messageContext, BackendAuthCo log.debug("Access token not available in TokenManager."); } try { - token = getAndAddNewToken(messageContext, privateKey, publicCert, trustStorePass, backendAuthConfig); + token = getAndAddNewToken(messageContext, privateKey, publicCert, backendAuthConfig); } catch (OpenHealthcareException e) { log.error("Error occurred while retrieving access token.", e); throw new OpenHealthcareRuntimeException(e); @@ -121,7 +125,7 @@ public String fetchValidAccessToken(MessageContext messageContext, BackendAuthCo * @throws OpenHealthcareException - when error occurred while retrieving token */ private synchronized Token getAndAddNewToken(MessageContext messageContext, Key privateKey, Certificate publicCert, - char[] trustStorePass, BackendAuthConfig config) + BackendAuthConfig config) throws OpenHealthcareException { String clientId = config.getClientId(); @@ -129,7 +133,7 @@ private synchronized Token getAndAddNewToken(MessageContext messageContext, Key Token token = TokenManager.getToken(clientId, tokenEndpoint); if (token == null || !token.isActive()) { String jwt = generateJWT(clientId, tokenEndpoint, privateKey, publicCert); - token = getAccessToken(messageContext, tokenEndpoint, jwt, config.isSSLEnabled(), trustStorePass); + token = getAccessToken(messageContext, tokenEndpoint, jwt); TokenManager.addToken(clientId, tokenEndpoint, token); } else { if (log.isDebugEnabled()) { @@ -189,20 +193,15 @@ private String generateJWT(String clientId, String tokenEndpoint, Key privateKey * @param messageContext - to set error response * @param tokenEndpoint - to retrieve access token * @param jwt - signed JWT including claims - * @param isSSLEnabled - whether SSL is enabled * @return Token * @throws OpenHealthcareException - when error occurred while retrieving access token */ private static Token getAccessToken( MessageContext messageContext, String tokenEndpoint, - String jwt, - boolean isSSLEnabled, - char[] trustStorePass) + String jwt) throws OpenHealthcareException { - long curTimeInMillis = System.currentTimeMillis(); - String trustStorePath = System.getProperty(Constants.NET_SSL_TRUST_STORE); HttpPost postRequest = new HttpPost(tokenEndpoint); ArrayList parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair(Constants.OAUTH2_GRANT_TYPE, Constants.CLIENT_CREDENTIALS)); @@ -210,78 +209,7 @@ private static Token getAccessToken( Constants.OAUTH2_CLIENT_ASSERTION_TYPE, Constants.OAUTH2_CLIENT_ASSERTION_TYPE_JWT_BEARER)); parameters.add(new BasicNameValuePair(Constants.OAUTH2_CLIENT_ASSERTION, jwt)); - try { - postRequest.setEntity(new UrlEncodedFormEntity(parameters)); - } catch (UnsupportedEncodingException e) { - String message = "Error occurred while preparing access token request payload."; - log.error(message); - throw new OpenHealthcareException(message, e); - } - CloseableHttpClient httpsClient; - - if (isSSLEnabled) { - httpsClient = Utils.getHttpsClient(trustStorePath, trustStorePass); - } else { - httpsClient = HttpClients.createDefault(); - } - CloseableHttpResponse response; - try { - response = httpsClient.execute(postRequest); - HttpEntity responseEntity = response.getEntity(); - if (responseEntity == null) { - String message = "Failed to retrieve access token : No entity received."; - log.error(message); - throw new OpenHealthcareException(message); - } - int responseStatus = response.getStatusLine().getStatusCode(); - String respMessage; - respMessage = EntityUtils.toString(responseEntity); - - if (responseStatus == HttpURLConnection.HTTP_OK) { - return extractToken(respMessage, curTimeInMillis); - } else { - handleTokenRequestError(messageContext, responseStatus, respMessage); - } - } catch (IOException e) { - throw new OpenHealthcareRuntimeException(e); - } - throw new OpenHealthcareException("Error occurred while retrieving access token."); - } - - /** - * Handle error response from the token endpoint. - * - * @param messageContext - to set error response - * @param responseStatus - HTTP status code - * @param respMessage - response message - * @throws OpenHealthcareException - to propagate the error - */ - private static void handleTokenRequestError(MessageContext messageContext, int responseStatus, String respMessage) throws OpenHealthcareException { - String message = "Error occurred while retrieving access token. Response: [Status : " + responseStatus + " Message: " + respMessage + "]"; - log.error(message); - OpenHealthcareException exp = new OpenHealthcareException(message); - Utils.setErrorResponse(messageContext, exp, responseStatus, message); - throw exp; - } - - /** - * Extract token from the response message. - * - * @param respMessage - response message - * @param curTimeInMillis - current time in milliseconds - * @return - Token - */ - private static Token extractToken(String respMessage, long curTimeInMillis) { - JsonElement jsonElement = parser.parse(respMessage); - JsonObject jsonObject = jsonElement.getAsJsonObject(); - String accessToken = jsonObject.get("access_token").getAsString(); - long expireIn = jsonObject.get("expires_in").getAsLong(); - - Token token = new Token(accessToken, curTimeInMillis, expireIn * 1000); - if (log.isDebugEnabled()) { - log.debug(token); - } - return token; + return Utils.initiateTokenRequest(messageContext, postRequest, parameters); } public String getType() { diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java index 8083120..aca3906 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java @@ -21,6 +21,9 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +/** + * In-memory implementation of Token Store. + */ public class InMemoryTokenStore implements TokenStore { private final Map TOKEN_MAP = new ConcurrentHashMap<>(2); diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/Token.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/Token.java index 30ac2c2..7cb599f 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/Token.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/Token.java @@ -18,6 +18,9 @@ package org.wso2.healthcare.apim.backendauth.tokenmgt; +/** + * Token model class to hold access token information. + */ public class Token { private final String accessToken; diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenManager.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenManager.java index 13a3aca..e74ab0b 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenManager.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/TokenManager.java @@ -22,6 +22,9 @@ import org.apache.commons.logging.LogFactory; import org.wso2.healthcare.apim.backendauth.Constants; +/** + * Token Manager class to manage access tokens. + */ public class TokenManager { private static final Log log = LogFactory.getLog(TokenManager.class); private static final TokenStore TOKEN_STORE = new InMemoryTokenStore(); diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java index c6224ac..68d55ca 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/BackendAuthConfig.java @@ -27,7 +27,6 @@ public class BackendAuthConfig { private String authEndpoint; private String clientId; private char[] client_secret; - private boolean isSSLEnabled; private String privateKeyAlias; private String authType; @@ -48,22 +47,14 @@ public void setClientId(String clientId) { this.clientId = clientId; } - public char[] getClient_secret() { + public char[] getClientSecret() { return client_secret; } - public void setClient_secret(char[] client_secret) { + public void setClientSecret(char[] client_secret) { this.client_secret = client_secret; } - public boolean isSSLEnabled() { - return isSSLEnabled; - } - - public void setSSLEnabled(boolean SSLEnabled) { - isSSLEnabled = SSLEnabled; - } - public String getPrivateKeyAlias() { return privateKeyAlias; } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java index 9fcb0b1..9b197e2 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.core/src/main/java/org/wso2/healthcare/apim/core/config/OpenHealthcareConfig.java @@ -580,20 +580,6 @@ private ScopeMgtConfig buildScopeMgtConfig() { } private Map buildBackendAuthConfig() throws OpenHealthcareException { -// BackendAuthConfig backendAuthConfig = new BackendAuthConfig(); -// Object backendAuthObj = config.get("healthcare.backend.auth"); -// if (backendAuthObj instanceof TomlTable) { -// TomlTable beAuthTable = (TomlTable) backendAuthObj; -// backendAuthConfig.setAuthEndpoint(beAuthTable.getString("token_endpoint")); -// backendAuthConfig.setClientId(beAuthTable.getString("client_id")); -// backendAuthConfig.setPrivateKeyAlias(beAuthTable.getString("private_key_alias")); -// backendAuthConfig.setSSLEnabled(Boolean.TRUE.equals(beAuthTable.getBoolean("is_ssl_enabled"))); -// String clientSecret = beAuthTable.getString("client_secret", () -> null); -// if (clientSecret != null) { -// backendAuthConfig.setClient_secret(resolveSecret(clientSecret)); -// } -// } -// return backendAuthConfig; Map backendAuthConfigs = new HashMap<>(); Object backendAuthConfigObject = config.get("healthcare.backend.auth"); @@ -618,10 +604,9 @@ private Map buildBackendAuthConfig() throws OpenHealt if (keyAlias != null) { backendAuthConfig.setPrivateKeyAlias(keyAlias); } - backendAuthConfig.setSSLEnabled(Boolean.TRUE.equals(beAuthTable.getBoolean("is_ssl_enabled"))); String clientSecret = beAuthTable.getString("client_secret", () -> null); if (clientSecret != null) { - backendAuthConfig.setClient_secret(resolveSecret(clientSecret)); + backendAuthConfig.setClientSecret(resolveSecret(clientSecret)); } backendAuthConfig.setAuthType(beAuthTable.getString("auth_type")); backendAuthConfigs.put(backendAuthConfig.getName(), backendAuthConfig); From 60cde3ce97168e923969ab7742fa1333dbe259f1 Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Thu, 5 Dec 2024 15:06:30 +0530 Subject: [PATCH 8/9] minor fixes --- .../resources/repository/conf/deployment.toml | 16 ++++++------- .../readme.md | 2 +- .../backendauth/BackendAuthenticator.java | 1 + .../apim/backendauth/Constants.java | 5 ---- .../healthcare/apim/backendauth/Utils.java | 1 - .../backendauth/impl/BackendAuthHandler.java | 5 +++- ...ClientCredentialsBackendAuthenticator.java | 5 ---- .../PrivateKeyJWTBackendAuthenticator.java | 23 ++++--------------- .../tokenmgt/InMemoryTokenStore.java | 2 +- 9 files changed, 20 insertions(+), 40 deletions(-) diff --git a/distribution/apim-accelerator/resources/repository/conf/deployment.toml b/distribution/apim-accelerator/resources/repository/conf/deployment.toml index a4e1b31..82c9bdb 100644 --- a/distribution/apim-accelerator/resources/repository/conf/deployment.toml +++ b/distribution/apim-accelerator/resources/repository/conf/deployment.toml @@ -418,16 +418,16 @@ policy.limit.time_unit = "min" patient_id_uri = "http://wso2.org/claims/patientId" patient_id_key = "patientId" -[[healthcare.backend.auth]] +#[[healthcare.backend.auth]] # Name of the authentication method. This name must be matched with the Backend Auth Config policy attribute in the - # - Replace Backend Auth Token policy. -name = "epic_pkjwt" -# Authentication type. Only pkjwt and client_credentials are supported atm. -auth_type = "pkjwt" -# External Auth server's Token endpoint URL. -token_endpoint = "https://localhost:9443/oauth2/token" -client_id = "client_id" -private_key_alias = "key_alias" +#name = "epic_pkjwt" +## Authentication type. Only pkjwt and client_credentials are supported atm. +#auth_type = "pkjwt" +## External Auth server's Token endpoint URL. +#token_endpoint = "https://localhost:9443/oauth2/token" +#client_id = "client_id" +#private_key_alias = "key_alias" #[[healthcare.backend.auth]] #name = "epic_client_credentials" diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md index 92c98d9..ed8d767 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/readme.md @@ -12,7 +12,7 @@ retrieved token. ```toml [[healthcare.backend.auth]] name = "backend-auth" -auth_type = "pkjwt" or "client-credentials" +auth_type = "pkjwt" #"pkjwt" or "client-credentials" token_endpoint = "https://localhost:9443/oauth2/token" client_id = "client_id" client_secret = "client_secret" # Only for Client Credentials flow diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java index a59a826..f3a0570 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/BackendAuthenticator.java @@ -108,6 +108,7 @@ public String getConfigName() { return configName; } + @SuppressWarnings("Setter will be called by the ESB config builder") public void setConfigName(String configName) { this.configName = configName; } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java index 243a5ae..50342d8 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Constants.java @@ -23,17 +23,12 @@ */ public class Constants { public static final String TOKEN_KEY_SEPARATOR = "_"; - public static final String PROPERTY_ACCESS_TOKEN = "_HC_INTERNAL_ACCESS_TOKEN_"; - - public static final int BAD_REQUEST_ERROR_CODE = 400; - public static final int INTERNAL_SERVER_ERROR_CODE = 500; public static final String HTTP_SC = "HTTP_SC"; public static final String TENANT_INFO_ID = "tenant.info.id"; public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 2; public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; public static final String HEADER_NAME_AUTHORIZATION = "Authorization"; public static final String HEADER_VALUE_BEARER = "Bearer "; - public static final String HEADER_VALUE_BASIC = "Basic "; public static final String POLICY_ATTR_AUTH_TYPE_CLIENT_CRED = "client-credentials"; public static final String POLICY_ATTR_AUTH_TYPE_PKJWT = "pkjwt"; diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java index 181ae6e..c5827de 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java @@ -62,7 +62,6 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.List; /** diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java index 3afd56b..4beb189 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java @@ -19,6 +19,7 @@ package org.wso2.healthcare.apim.backendauth.impl; import org.apache.synapse.MessageContext; +import org.wso2.healthcare.apim.backendauth.Constants; import org.wso2.healthcare.apim.core.config.BackendAuthConfig; /** @@ -37,5 +38,7 @@ public interface BackendAuthHandler { /** * Get the auth header scheme. i.e. Bearer, Basic etc. */ - String getAuthHeaderScheme(); + default String getAuthHeaderScheme(){ + return Constants.HEADER_VALUE_BEARER; + } } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java index e021454..cb5c5da 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java @@ -50,11 +50,6 @@ public String fetchValidAccessToken(MessageContext messageContext, BackendAuthCo return accessToken; } - @Override - public String getAuthHeaderScheme() { - return Constants.HEADER_VALUE_BEARER; - } - /** * Get and add a new token to the token store. * diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/PrivateKeyJWTBackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/PrivateKeyJWTBackendAuthenticator.java index 0c50a29..c402d6f 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/PrivateKeyJWTBackendAuthenticator.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/PrivateKeyJWTBackendAuthenticator.java @@ -18,25 +18,20 @@ package org.wso2.healthcare.apim.backendauth.impl; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.nimbusds.jose.*; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; import org.apache.synapse.MessageContext; -import org.jetbrains.annotations.NotNull; import org.wso2.carbon.core.util.KeyStoreManager; import org.wso2.carbon.utils.CarbonUtils; import org.wso2.healthcare.apim.backendauth.Constants; @@ -47,9 +42,6 @@ import org.wso2.healthcare.apim.core.OpenHealthcareRuntimeException; import org.wso2.healthcare.apim.core.config.BackendAuthConfig; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; import java.security.Key; import java.security.KeyStore; import java.security.cert.Certificate; @@ -66,11 +58,6 @@ public class PrivateKeyJWTBackendAuthenticator implements BackendAuthHandler { private static final Log log = LogFactory.getLog(PrivateKeyJWTBackendAuthenticator.class); - @Override - public String getAuthHeaderScheme() { - return Constants.HEADER_VALUE_BEARER; - } - @Override public String fetchValidAccessToken(MessageContext messageContext, BackendAuthConfig backendAuthConfig) { diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java index aca3906..4db867e 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/tokenmgt/InMemoryTokenStore.java @@ -51,4 +51,4 @@ public void clean() { TOKEN_MAP.clear(); } -} \ No newline at end of file +} From a78bbae4675e7f45f0f609cf2c79834eb0023d51 Mon Sep 17 00:00:00 2001 From: isuruh15 Date: Thu, 5 Dec 2024 15:40:54 +0530 Subject: [PATCH 9/9] formatting fixes --- .../healthcare/apim/backendauth/Utils.java | 3 ++- .../backendauth/impl/BackendAuthHandler.java | 2 +- ...ClientCredentialsBackendAuthenticator.java | 21 ++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java index c5827de..260fb7e 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/Utils.java @@ -95,7 +95,7 @@ public static void setErrorResponse(MessageContext messageContext, Throwable e, */ public static CloseableHttpClient getHttpsClient(String trustStorePath, char[] trustStorePass) throws OpenHealthcareException { - SSLConnectionSocketFactory sslsf = createSSLConnectionSocketFactory(trustStorePath,trustStorePass); + SSLConnectionSocketFactory sslsf = createSSLConnectionSocketFactory(trustStorePath, trustStorePass); Registry socketFactoryRegistry = RegistryBuilder.create() .register(HTTP_PROTOCOL, new PlainConnectionSocketFactory()) @@ -109,6 +109,7 @@ public static CloseableHttpClient getHttpsClient(String trustStorePath, char[] t /** * Get the pooling http client connection manager. + * * @param socketFactoryRegistry - Socket factory registry * @return PoolingHttpClientConnectionManager */ diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java index 4beb189..fd375f8 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/BackendAuthHandler.java @@ -38,7 +38,7 @@ public interface BackendAuthHandler { /** * Get the auth header scheme. i.e. Bearer, Basic etc. */ - default String getAuthHeaderScheme(){ + default String getAuthHeaderScheme() { return Constants.HEADER_VALUE_BEARER; } } diff --git a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java index cb5c5da..01d20cc 100644 --- a/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java +++ b/product-accelerators/apim/components/org.wso2.healthcare.apim.backendauth/src/main/java/org/wso2/healthcare/apim/backendauth/impl/ClientCredentialsBackendAuthenticator.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.wso2.healthcare.apim.backendauth.impl; import org.apache.commons.logging.Log; @@ -19,9 +37,10 @@ /** * Mediator to authenticate with the backend using client credentials. */ -public class ClientCredentialsBackendAuthenticator implements BackendAuthHandler{ +public class ClientCredentialsBackendAuthenticator implements BackendAuthHandler { private static final Log log = LogFactory.getLog(ClientCredentialsBackendAuthenticator.class); + @Override public String fetchValidAccessToken(MessageContext messageContext, BackendAuthConfig backendAuthConfig) { log.info("PrivateKeyJWTBackendAuthenticator mediator is started.");