This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 2b08741b74b CAMEL-20503: camel-http OAuth2 support for caching /
refreshing tokens (#16227)
2b08741b74b is described below
commit 2b08741b74bece6c54be5bcf0f08995d78d9fc44
Author: Ivan Kulaga <[email protected]>
AuthorDate: Wed Nov 13 11:14:08 2024 +0500
CAMEL-20503: camel-http OAuth2 support for caching / refreshing tokens
(#16227)
* CAMEL-20503: camel-http OAuth2 support for caching / refreshing tokens
- added caching oauth2 tokens for http component, and configuration
parameters for caching
* CAMEL-20503: camel-http OAuth2 support for caching / refreshing tokens
- added new oauth2 options to the exclude list in WebsocketEndpoint
---
.../org/apache/camel/catalog/components/http.json | 15 +-
.../org/apache/camel/catalog/components/https.json | 15 +-
.../atmosphere/websocket/WebsocketEndpoint.java | 2 +-
.../camel/http/common/HttpCommonEndpoint.java | 51 ++++++
.../camel/http/common/HttpConfiguration.java | 52 ++++++
.../component/http/HttpEndpointConfigurer.java | 18 ++
.../component/http/HttpEndpointUriFactory.java | 5 +-
.../org/apache/camel/component/http/http.json | 15 +-
.../org/apache/camel/component/http/https.json | 15 +-
.../apache/camel/component/http/HttpComponent.java | 26 ++-
.../component/http/OAuth2ClientConfigurer.java | 150 +++++++++++++---
.../component/http/HttpOAuth2TokenCachingTest.java | 189 +++++++++++++++++++++
.../http/handler/OAuth2TokenRequestHandler.java | 1 -
.../endpoint/dsl/HttpEndpointBuilderFactory.java | 106 ++++++++++++
14 files changed, 605 insertions(+), 55 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json
index caa43c1b699..2e2f0473f08 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json
@@ -134,11 +134,14 @@
"authMethodPriority": { "index": 46, "kind": "parameter", "displayName":
"Auth Method Priority", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String", "enum": [
"Basic", "Digest", "NTLM" ], "deprecated": false, "autowired": false, "secret":
false, "description": "Which authentication method to prioritize to use, either
as Basic, Digest or NTLM." },
"authPassword": { "index": 47, "kind": "parameter", "displayName": "Auth
Password", "group": "security", "label": "producer,security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "description": "Authentication password" },
"authUsername": { "index": 48, "kind": "parameter", "displayName": "Auth
Username", "group": "security", "label": "producer,security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "description": "Authentication username" },
- "oauth2ClientId": { "index": 49, "kind": "parameter", "displayName":
"Oauth2 Client Id", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client id" },
- "oauth2ClientSecret": { "index": 50, "kind": "parameter", "displayName":
"Oauth2 Client Secret", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client secret" },
- "oauth2Scope": { "index": 51, "kind": "parameter", "displayName": "Oauth2
Scope", "group": "security", "label": "producer,security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "OAuth2 scope" },
- "oauth2TokenEndpoint": { "index": 52, "kind": "parameter", "displayName":
"Oauth2 Token Endpoint", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description":
"OAuth2 Token endpoint" },
- "sslContextParameters": { "index": 53, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "description": "To configure security
using SSLContextParameters. Important: Only one instance of
org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent.
If you [...]
- "x509HostnameVerifier": { "index": 54, "kind": "parameter", "displayName":
"X509 Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "description": "To
use a custom X509HostnameVerifier such as DefaultHostnameVerifier or
NoopHostnameVerifier" }
+ "oauth2CachedTokensDefaultExpirySeconds": { "index": 49, "kind":
"parameter", "displayName": "Oauth2 Cached Tokens Default Expiry Seconds",
"group": "security", "label": "producer,security", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 3600, "description": "Default expiration time
for cached OAuth2 tokens, in seconds. Used if token response does not contain
'expires_in' field." },
+ "oauth2CachedTokensExpirationMarginSeconds": { "index": 50, "kind":
"parameter", "displayName": "Oauth2 Cached Tokens Expiration Margin Seconds",
"group": "security", "label": "producer,security", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 5, "description": "Amount of time which is
deducted from OAuth2 tokens expiry time to compensate for the time it takes
OAuth2 Token Endpoint to send the token [...]
+ "oauth2CacheTokens": { "index": 51, "kind": "parameter", "displayName":
"Oauth2 Cache Tokens", "group": "security", "label": "producer,security",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "defaultValue": false,
"description": "Whether to cache OAuth2 client tokens." },
+ "oauth2ClientId": { "index": 52, "kind": "parameter", "displayName":
"Oauth2 Client Id", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client id" },
+ "oauth2ClientSecret": { "index": 53, "kind": "parameter", "displayName":
"Oauth2 Client Secret", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client secret" },
+ "oauth2Scope": { "index": 54, "kind": "parameter", "displayName": "Oauth2
Scope", "group": "security", "label": "producer,security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "OAuth2 scope" },
+ "oauth2TokenEndpoint": { "index": 55, "kind": "parameter", "displayName":
"Oauth2 Token Endpoint", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description":
"OAuth2 Token endpoint" },
+ "sslContextParameters": { "index": 56, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "description": "To configure security
using SSLContextParameters. Important: Only one instance of
org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent.
If you [...]
+ "x509HostnameVerifier": { "index": 57, "kind": "parameter", "displayName":
"X509 Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "description": "To
use a custom X509HostnameVerifier such as DefaultHostnameVerifier or
NoopHostnameVerifier" }
}
}
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json
index 269f80bfe23..d897219586d 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json
@@ -134,11 +134,14 @@
"authMethodPriority": { "index": 46, "kind": "parameter", "displayName":
"Auth Method Priority", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String", "enum": [
"Basic", "Digest", "NTLM" ], "deprecated": false, "autowired": false, "secret":
false, "description": "Which authentication method to prioritize to use, either
as Basic, Digest or NTLM." },
"authPassword": { "index": 47, "kind": "parameter", "displayName": "Auth
Password", "group": "security", "label": "producer,security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "description": "Authentication password" },
"authUsername": { "index": 48, "kind": "parameter", "displayName": "Auth
Username", "group": "security", "label": "producer,security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "description": "Authentication username" },
- "oauth2ClientId": { "index": 49, "kind": "parameter", "displayName":
"Oauth2 Client Id", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client id" },
- "oauth2ClientSecret": { "index": 50, "kind": "parameter", "displayName":
"Oauth2 Client Secret", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client secret" },
- "oauth2Scope": { "index": 51, "kind": "parameter", "displayName": "Oauth2
Scope", "group": "security", "label": "producer,security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "OAuth2 scope" },
- "oauth2TokenEndpoint": { "index": 52, "kind": "parameter", "displayName":
"Oauth2 Token Endpoint", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description":
"OAuth2 Token endpoint" },
- "sslContextParameters": { "index": 53, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "description": "To configure security
using SSLContextParameters. Important: Only one instance of
org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent.
If you [...]
- "x509HostnameVerifier": { "index": 54, "kind": "parameter", "displayName":
"X509 Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "description": "To
use a custom X509HostnameVerifier such as DefaultHostnameVerifier or
NoopHostnameVerifier" }
+ "oauth2CachedTokensDefaultExpirySeconds": { "index": 49, "kind":
"parameter", "displayName": "Oauth2 Cached Tokens Default Expiry Seconds",
"group": "security", "label": "producer,security", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 3600, "description": "Default expiration time
for cached OAuth2 tokens, in seconds. Used if token response does not contain
'expires_in' field." },
+ "oauth2CachedTokensExpirationMarginSeconds": { "index": 50, "kind":
"parameter", "displayName": "Oauth2 Cached Tokens Expiration Margin Seconds",
"group": "security", "label": "producer,security", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 5, "description": "Amount of time which is
deducted from OAuth2 tokens expiry time to compensate for the time it takes
OAuth2 Token Endpoint to send the token [...]
+ "oauth2CacheTokens": { "index": 51, "kind": "parameter", "displayName":
"Oauth2 Cache Tokens", "group": "security", "label": "producer,security",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "defaultValue": false,
"description": "Whether to cache OAuth2 client tokens." },
+ "oauth2ClientId": { "index": 52, "kind": "parameter", "displayName":
"Oauth2 Client Id", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client id" },
+ "oauth2ClientSecret": { "index": 53, "kind": "parameter", "displayName":
"Oauth2 Client Secret", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client secret" },
+ "oauth2Scope": { "index": 54, "kind": "parameter", "displayName": "Oauth2
Scope", "group": "security", "label": "producer,security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "OAuth2 scope" },
+ "oauth2TokenEndpoint": { "index": 55, "kind": "parameter", "displayName":
"Oauth2 Token Endpoint", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description":
"OAuth2 Token endpoint" },
+ "sslContextParameters": { "index": 56, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "description": "To configure security
using SSLContextParameters. Important: Only one instance of
org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent.
If you [...]
+ "x509HostnameVerifier": { "index": 57, "kind": "parameter", "displayName":
"X509 Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "description": "To
use a custom X509HostnameVerifier such as DefaultHostnameVerifier or
NoopHostnameVerifier" }
}
}
diff --git
a/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpoint.java
b/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpoint.java
index 186ddae6813..87fb493d523 100644
---
a/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpoint.java
+++
b/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpoint.java
@@ -41,7 +41,7 @@ import org.apache.camel.util.StringHelper;
+
"copyHeaders,httpMethod,ignoreResponseBody,preserveHostHeader,throwExceptionOnFailure,okStatusCodeRange,"
+
"proxyAuthScheme,proxyAuthMethod,proxyAuthUsername,proxyAuthPassword,proxyAuthHost,proxyAuthPort,proxyAuthDomain,"
+
"proxyAuthNtHost,proxyAuthScheme,proxyHost,proxyPort,"
- +
"oauth2ClientId,oauth2ClientSecret,oauth2TokenEndpoint,oauth2Scope",
+ +
"oauth2ClientId,oauth2ClientSecret,oauth2TokenEndpoint,oauth2Scope,oauth2CacheTokens,oauth2CachedTokensDefaultExpirySeconds,oauth2CachedTokensExpirationMarginSeconds",
annotations = {
"protocol=http",
})
diff --git
a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
index 7ad028318c3..0d04b2514d1 100644
---
a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
+++
b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
@@ -162,6 +162,19 @@ public abstract class HttpCommonEndpoint extends
DefaultEndpoint
private String oauth2TokenEndpoint;
@UriParam(label = "producer,security", description = "OAuth2 scope")
private String oauth2Scope;
+ @UriParam(label = "producer,security", defaultValue = "false",
+ description = "Whether to cache OAuth2 client tokens.")
+ private boolean oauth2CacheTokens = false;
+ @UriParam(label = "producer,security", defaultValue = "3600",
+ description = "Default expiration time for cached OAuth2 tokens,
in seconds. Used if token response does not contain 'expires_in' field.")
+ private long oauth2CachedTokensDefaultExpirySeconds = 3600L;
+ @UriParam(label = "producer,security", defaultValue = "5",
+ description = "Amount of time which is deducted from OAuth2
tokens expiry time to compensate for the time it takes OAuth2 Token Endpoint to
send the token over http, in seconds. "
+ +
+ "Set this parameter to high value if you OAuth2
Token Endpoint answers slowly or you tokens expire quickly. "
+ +
+ "If you set this parameter to too small value, you
can get 4xx http errors because camel will think that the received token is
still valid, while in reality the token is expired for the Authentication
server.")
+ private long oauth2CachedTokensExpirationMarginSeconds = 5L;
@UriParam(label = "producer,security", description = "Authentication
domain to use with NTML")
private String authDomain;
@UriParam(label = "producer,security", description = "Authentication host
to use with NTML")
@@ -843,4 +856,42 @@ public abstract class HttpCommonEndpoint extends
DefaultEndpoint
public void setOauth2Scope(String oauth2Scope) {
this.oauth2Scope = oauth2Scope;
}
+
+ public boolean isOauth2CacheTokens() {
+ return oauth2CacheTokens;
+ }
+
+ /**
+ * Whether to cache OAuth2 client tokens.
+ */
+ public void setOauth2CacheTokens(boolean oauth2CacheTokens) {
+ this.oauth2CacheTokens = oauth2CacheTokens;
+ }
+
+ public long getOauth2CachedTokensDefaultExpirySeconds() {
+ return oauth2CachedTokensDefaultExpirySeconds;
+ }
+
+ /**
+ * Default expiration time for cached OAuth2 tokens, in seconds. Used if
token response does not contain
+ * 'expires_in' field.
+ */
+ public void setOauth2CachedTokensDefaultExpirySeconds(long
oauth2CachedTokensDefaultExpirySeconds) {
+ this.oauth2CachedTokensDefaultExpirySeconds =
oauth2CachedTokensDefaultExpirySeconds;
+ }
+
+ public long getOauth2CachedTokensExpirationMarginSeconds() {
+ return oauth2CachedTokensExpirationMarginSeconds;
+ }
+
+ /**
+ * Amount of time which is deducted from OAuth2 tokens expiry time to
compensate for the time it takes OAuth2 Token
+ * Endpoint to send the token over http, in seconds. Set this parameter to
high value if you OAuth2 Token Endpoint
+ * answers slowly or you tokens expire quickly. If you set this parameter
to too small value, you can get 4xx http
+ * errors because camel will think that the received token is still valid,
while in reality the token is expired for
+ * the Authentication server.
+ */
+ public void setOauth2CachedTokensExpirationMarginSeconds(long
cachedTokensExpirationMarginSeconds) {
+ this.oauth2CachedTokensExpirationMarginSeconds =
cachedTokensExpirationMarginSeconds;
+ }
}
diff --git
a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java
b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java
index c0a404ab59c..4975645a296 100644
---
a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java
+++
b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java
@@ -19,6 +19,7 @@ package org.apache.camel.http.common;
import java.io.Serializable;
import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriParam;
public class HttpConfiguration implements Serializable {
private static final long serialVersionUID = 1L;
@@ -41,6 +42,19 @@ public class HttpConfiguration implements Serializable {
private String oauth2TokenEndpoint;
@Metadata(label = "producer,security", description = "OAuth2 scope")
private String oauth2Scope;
+ @UriParam(label = "producer,security", defaultValue = "false",
+ description = "Whether to cache OAuth2 client tokens.")
+ private boolean oauth2CacheTokens = false;
+ @UriParam(label = "producer,security", defaultValue = "3600",
+ description = "Default expiration time for cached OAuth2 tokens,
in seconds. Used if token response does not contain 'expires_in' field.")
+ private long oauth2CachedTokensDefaultExpirySeconds = 3600L;
+ @UriParam(label = "producer,security", defaultValue = "5",
+ description = "Amount of time which is deducted from OAuth2
tokens expiry time to compensate for the time it takes OAuth2 Token Endpoint to
send the token over http, in seconds. "
+ +
+ "Set this parameter to high value if you OAuth2
Token Endpoint answers slowly or you tokens expire quickly. "
+ +
+ "If you set this parameter to too small value, you
can get 4xx http errors because camel will think that the received token is
still valid, while in reality the token is expired for the Authentication
server.")
+ private long oauth2CachedTokensExpirationMarginSeconds = 5L;
@Metadata(label = "producer,security", description = "Authentication
domain to use with NTML")
private String authDomain;
@Metadata(label = "producer,security", description = "Authentication host
to use with NTML")
@@ -272,4 +286,42 @@ public class HttpConfiguration implements Serializable {
public void setOauth2Scope(String oauth2Scope) {
this.oauth2Scope = oauth2Scope;
}
+
+ public boolean isOauth2CacheTokens() {
+ return oauth2CacheTokens;
+ }
+
+ /**
+ * Whether to cache OAuth2 client tokens.
+ */
+ public void setOauth2CacheTokens(boolean oauth2CacheTokens) {
+ this.oauth2CacheTokens = oauth2CacheTokens;
+ }
+
+ public long getOauth2CachedTokensDefaultExpirySeconds() {
+ return oauth2CachedTokensDefaultExpirySeconds;
+ }
+
+ /**
+ * Default expiration time for cached OAuth2 tokens, in seconds. Used if
token response does not contain
+ * 'expires_in' field.
+ */
+ public void setOauth2CachedTokensDefaultExpirySeconds(long
oauth2CachedTokensDefaultExpirySeconds) {
+ this.oauth2CachedTokensDefaultExpirySeconds =
oauth2CachedTokensDefaultExpirySeconds;
+ }
+
+ public long getOauth2CachedTokensExpirationMarginSeconds() {
+ return oauth2CachedTokensExpirationMarginSeconds;
+ }
+
+ /**
+ * Amount of time which is deducted from OAuth2 tokens expiry time to
compensate for the time it takes OAuth2 Token
+ * Endpoint to send the token over http, in seconds. Set this parameter to
high value if you OAuth2 Token Endpoint
+ * answers slowly or you tokens expire quickly. If you set this parameter
to too small value, you can get 4xx http
+ * errors because camel will think that the received token is still valid,
while in reality the token is expired for
+ * the Authentication server.
+ */
+ public void setOauth2CachedTokensExpirationMarginSeconds(long
oauth2CachedTokensExpirationMarginSeconds) {
+ this.oauth2CachedTokensExpirationMarginSeconds =
oauth2CachedTokensExpirationMarginSeconds;
+ }
}
diff --git
a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointConfigurer.java
b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointConfigurer.java
index 07c2d800785..4b10ca79243 100644
---
a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointConfigurer.java
+++
b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointConfigurer.java
@@ -85,6 +85,12 @@ public class HttpEndpointConfigurer extends
PropertyConfigurerSupport implements
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
case "maxtotalconnections":
case "maxTotalConnections":
target.setMaxTotalConnections(property(camelContext, int.class, value)); return
true;
+ case "oauth2cachetokens":
+ case "oauth2CacheTokens":
target.setOauth2CacheTokens(property(camelContext, boolean.class, value));
return true;
+ case "oauth2cachedtokensdefaultexpiryseconds":
+ case "oauth2CachedTokensDefaultExpirySeconds":
target.setOauth2CachedTokensDefaultExpirySeconds(property(camelContext,
long.class, value)); return true;
+ case "oauth2cachedtokensexpirationmarginseconds":
+ case "oauth2CachedTokensExpirationMarginSeconds":
target.setOauth2CachedTokensExpirationMarginSeconds(property(camelContext,
long.class, value)); return true;
case "oauth2clientid":
case "oauth2ClientId": target.setOauth2ClientId(property(camelContext,
java.lang.String.class, value)); return true;
case "oauth2clientsecret":
@@ -200,6 +206,12 @@ public class HttpEndpointConfigurer extends
PropertyConfigurerSupport implements
case "lazyStartProducer": return boolean.class;
case "maxtotalconnections":
case "maxTotalConnections": return int.class;
+ case "oauth2cachetokens":
+ case "oauth2CacheTokens": return boolean.class;
+ case "oauth2cachedtokensdefaultexpiryseconds":
+ case "oauth2CachedTokensDefaultExpirySeconds": return long.class;
+ case "oauth2cachedtokensexpirationmarginseconds":
+ case "oauth2CachedTokensExpirationMarginSeconds": return long.class;
case "oauth2clientid":
case "oauth2ClientId": return java.lang.String.class;
case "oauth2clientsecret":
@@ -316,6 +328,12 @@ public class HttpEndpointConfigurer extends
PropertyConfigurerSupport implements
case "lazyStartProducer": return target.isLazyStartProducer();
case "maxtotalconnections":
case "maxTotalConnections": return target.getMaxTotalConnections();
+ case "oauth2cachetokens":
+ case "oauth2CacheTokens": return target.isOauth2CacheTokens();
+ case "oauth2cachedtokensdefaultexpiryseconds":
+ case "oauth2CachedTokensDefaultExpirySeconds": return
target.getOauth2CachedTokensDefaultExpirySeconds();
+ case "oauth2cachedtokensexpirationmarginseconds":
+ case "oauth2CachedTokensExpirationMarginSeconds": return
target.getOauth2CachedTokensExpirationMarginSeconds();
case "oauth2clientid":
case "oauth2ClientId": return target.getOauth2ClientId();
case "oauth2clientsecret":
diff --git
a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointUriFactory.java
b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointUriFactory.java
index 76185081941..98e1aa9f5dc 100644
---
a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointUriFactory.java
+++
b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointUriFactory.java
@@ -24,7 +24,7 @@ public class HttpEndpointUriFactory extends
org.apache.camel.support.component.E
private static final Set<String> SECRET_PROPERTY_NAMES;
private static final Set<String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(55);
+ Set<String> props = new HashSet<>(58);
props.add("authDomain");
props.add("authHost");
props.add("authMethod");
@@ -57,6 +57,9 @@ public class HttpEndpointUriFactory extends
org.apache.camel.support.component.E
props.add("ignoreResponseBody");
props.add("lazyStartProducer");
props.add("maxTotalConnections");
+ props.add("oauth2CacheTokens");
+ props.add("oauth2CachedTokensDefaultExpirySeconds");
+ props.add("oauth2CachedTokensExpirationMarginSeconds");
props.add("oauth2ClientId");
props.add("oauth2ClientSecret");
props.add("oauth2Scope");
diff --git
a/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/http.json
b/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/http.json
index caa43c1b699..2e2f0473f08 100644
---
a/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/http.json
+++
b/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/http.json
@@ -134,11 +134,14 @@
"authMethodPriority": { "index": 46, "kind": "parameter", "displayName":
"Auth Method Priority", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String", "enum": [
"Basic", "Digest", "NTLM" ], "deprecated": false, "autowired": false, "secret":
false, "description": "Which authentication method to prioritize to use, either
as Basic, Digest or NTLM." },
"authPassword": { "index": 47, "kind": "parameter", "displayName": "Auth
Password", "group": "security", "label": "producer,security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "description": "Authentication password" },
"authUsername": { "index": 48, "kind": "parameter", "displayName": "Auth
Username", "group": "security", "label": "producer,security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "description": "Authentication username" },
- "oauth2ClientId": { "index": 49, "kind": "parameter", "displayName":
"Oauth2 Client Id", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client id" },
- "oauth2ClientSecret": { "index": 50, "kind": "parameter", "displayName":
"Oauth2 Client Secret", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client secret" },
- "oauth2Scope": { "index": 51, "kind": "parameter", "displayName": "Oauth2
Scope", "group": "security", "label": "producer,security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "OAuth2 scope" },
- "oauth2TokenEndpoint": { "index": 52, "kind": "parameter", "displayName":
"Oauth2 Token Endpoint", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description":
"OAuth2 Token endpoint" },
- "sslContextParameters": { "index": 53, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "description": "To configure security
using SSLContextParameters. Important: Only one instance of
org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent.
If you [...]
- "x509HostnameVerifier": { "index": 54, "kind": "parameter", "displayName":
"X509 Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "description": "To
use a custom X509HostnameVerifier such as DefaultHostnameVerifier or
NoopHostnameVerifier" }
+ "oauth2CachedTokensDefaultExpirySeconds": { "index": 49, "kind":
"parameter", "displayName": "Oauth2 Cached Tokens Default Expiry Seconds",
"group": "security", "label": "producer,security", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 3600, "description": "Default expiration time
for cached OAuth2 tokens, in seconds. Used if token response does not contain
'expires_in' field." },
+ "oauth2CachedTokensExpirationMarginSeconds": { "index": 50, "kind":
"parameter", "displayName": "Oauth2 Cached Tokens Expiration Margin Seconds",
"group": "security", "label": "producer,security", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 5, "description": "Amount of time which is
deducted from OAuth2 tokens expiry time to compensate for the time it takes
OAuth2 Token Endpoint to send the token [...]
+ "oauth2CacheTokens": { "index": 51, "kind": "parameter", "displayName":
"Oauth2 Cache Tokens", "group": "security", "label": "producer,security",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "defaultValue": false,
"description": "Whether to cache OAuth2 client tokens." },
+ "oauth2ClientId": { "index": 52, "kind": "parameter", "displayName":
"Oauth2 Client Id", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client id" },
+ "oauth2ClientSecret": { "index": 53, "kind": "parameter", "displayName":
"Oauth2 Client Secret", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client secret" },
+ "oauth2Scope": { "index": 54, "kind": "parameter", "displayName": "Oauth2
Scope", "group": "security", "label": "producer,security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "OAuth2 scope" },
+ "oauth2TokenEndpoint": { "index": 55, "kind": "parameter", "displayName":
"Oauth2 Token Endpoint", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description":
"OAuth2 Token endpoint" },
+ "sslContextParameters": { "index": 56, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "description": "To configure security
using SSLContextParameters. Important: Only one instance of
org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent.
If you [...]
+ "x509HostnameVerifier": { "index": 57, "kind": "parameter", "displayName":
"X509 Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "description": "To
use a custom X509HostnameVerifier such as DefaultHostnameVerifier or
NoopHostnameVerifier" }
}
}
diff --git
a/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/https.json
b/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/https.json
index 269f80bfe23..d897219586d 100644
---
a/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/https.json
+++
b/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/https.json
@@ -134,11 +134,14 @@
"authMethodPriority": { "index": 46, "kind": "parameter", "displayName":
"Auth Method Priority", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String", "enum": [
"Basic", "Digest", "NTLM" ], "deprecated": false, "autowired": false, "secret":
false, "description": "Which authentication method to prioritize to use, either
as Basic, Digest or NTLM." },
"authPassword": { "index": 47, "kind": "parameter", "displayName": "Auth
Password", "group": "security", "label": "producer,security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "description": "Authentication password" },
"authUsername": { "index": 48, "kind": "parameter", "displayName": "Auth
Username", "group": "security", "label": "producer,security", "required":
false, "type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": true, "description": "Authentication username" },
- "oauth2ClientId": { "index": 49, "kind": "parameter", "displayName":
"Oauth2 Client Id", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client id" },
- "oauth2ClientSecret": { "index": 50, "kind": "parameter", "displayName":
"Oauth2 Client Secret", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client secret" },
- "oauth2Scope": { "index": 51, "kind": "parameter", "displayName": "Oauth2
Scope", "group": "security", "label": "producer,security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "OAuth2 scope" },
- "oauth2TokenEndpoint": { "index": 52, "kind": "parameter", "displayName":
"Oauth2 Token Endpoint", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description":
"OAuth2 Token endpoint" },
- "sslContextParameters": { "index": 53, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "description": "To configure security
using SSLContextParameters. Important: Only one instance of
org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent.
If you [...]
- "x509HostnameVerifier": { "index": 54, "kind": "parameter", "displayName":
"X509 Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "description": "To
use a custom X509HostnameVerifier such as DefaultHostnameVerifier or
NoopHostnameVerifier" }
+ "oauth2CachedTokensDefaultExpirySeconds": { "index": 49, "kind":
"parameter", "displayName": "Oauth2 Cached Tokens Default Expiry Seconds",
"group": "security", "label": "producer,security", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 3600, "description": "Default expiration time
for cached OAuth2 tokens, in seconds. Used if token response does not contain
'expires_in' field." },
+ "oauth2CachedTokensExpirationMarginSeconds": { "index": 50, "kind":
"parameter", "displayName": "Oauth2 Cached Tokens Expiration Margin Seconds",
"group": "security", "label": "producer,security", "required": false, "type":
"integer", "javaType": "long", "deprecated": false, "autowired": false,
"secret": false, "defaultValue": 5, "description": "Amount of time which is
deducted from OAuth2 tokens expiry time to compensate for the time it takes
OAuth2 Token Endpoint to send the token [...]
+ "oauth2CacheTokens": { "index": 51, "kind": "parameter", "displayName":
"Oauth2 Cache Tokens", "group": "security", "label": "producer,security",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "defaultValue": false,
"description": "Whether to cache OAuth2 client tokens." },
+ "oauth2ClientId": { "index": 52, "kind": "parameter", "displayName":
"Oauth2 Client Id", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client id" },
+ "oauth2ClientSecret": { "index": 53, "kind": "parameter", "displayName":
"Oauth2 Client Secret", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": true, "description": "OAuth2
client secret" },
+ "oauth2Scope": { "index": 54, "kind": "parameter", "displayName": "Oauth2
Scope", "group": "security", "label": "producer,security", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"autowired": false, "secret": false, "description": "OAuth2 scope" },
+ "oauth2TokenEndpoint": { "index": 55, "kind": "parameter", "displayName":
"Oauth2 Token Endpoint", "group": "security", "label": "producer,security",
"required": false, "type": "string", "javaType": "java.lang.String",
"deprecated": false, "autowired": false, "secret": false, "description":
"OAuth2 Token endpoint" },
+ "sslContextParameters": { "index": 56, "kind": "parameter", "displayName":
"Ssl Context Parameters", "group": "security", "label": "security", "required":
false, "type": "object", "javaType":
"org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false,
"autowired": false, "secret": false, "description": "To configure security
using SSLContextParameters. Important: Only one instance of
org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent.
If you [...]
+ "x509HostnameVerifier": { "index": 57, "kind": "parameter", "displayName":
"X509 Hostname Verifier", "group": "security", "label": "security", "required":
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier",
"deprecated": false, "autowired": false, "secret": false, "description": "To
use a custom X509HostnameVerifier such as DefaultHostnameVerifier or
NoopHostnameVerifier" }
}
}
diff --git
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java
index 23c4138dcd5..b715d065716 100644
---
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java
+++
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java
@@ -33,6 +33,7 @@ import
org.apache.camel.component.extension.ComponentVerifierExtension;
import org.apache.camel.http.base.HttpHelper;
import org.apache.camel.http.common.HttpBinding;
import org.apache.camel.http.common.HttpCommonComponent;
+import org.apache.camel.http.common.HttpConfiguration;
import org.apache.camel.http.common.HttpRestHeaderFilterStrategy;
import org.apache.camel.spi.BeanIntrospection;
import org.apache.camel.spi.HeaderFilterStrategy;
@@ -235,10 +236,33 @@ public class HttpComponent extends HttpCommonComponent
implements RestProducerFa
String clientSecret = getParameter(parameters, "oauth2ClientSecret",
String.class);
String tokenEndpoint = getParameter(parameters, "oauth2TokenEndpoint",
String.class);
String scope = getParameter(parameters, "oauth2Scope", String.class);
+ HttpConfiguration configDefaults = new HttpConfiguration();
+ boolean cacheTokens = getParameter(
+ parameters,
+ "oauth2CacheTokens",
+ boolean.class,
+ configDefaults.isOauth2CacheTokens());
+ long cachedTokensDefaultExpirySeconds = getParameter(
+ parameters,
+ "oauth2CachedTokensDefaultExpirySeconds",
+ long.class,
+ configDefaults.getOauth2CachedTokensDefaultExpirySeconds());
+ long cachedTokensExpirationMarginSeconds = getParameter(
+ parameters,
+ "oauth2CachedTokensExpirationMarginSeconds",
+ long.class,
+ configDefaults.getOauth2CachedTokensExpirationMarginSeconds());
if (clientId != null && clientSecret != null && tokenEndpoint != null)
{
return CompositeHttpConfigurer.combineConfigurers(configurer,
- new OAuth2ClientConfigurer(clientId, clientSecret,
tokenEndpoint, scope));
+ new OAuth2ClientConfigurer(
+ clientId,
+ clientSecret,
+ tokenEndpoint,
+ scope,
+ cacheTokens,
+ cachedTokensDefaultExpirySeconds,
+ cachedTokensExpirationMarginSeconds));
}
return configurer;
}
diff --git
a/components/camel-http/src/main/java/org/apache/camel/component/http/OAuth2ClientConfigurer.java
b/components/camel-http/src/main/java/org/apache/camel/component/http/OAuth2ClientConfigurer.java
index 0701ba6b654..ee29c73b8c6 100644
---
a/components/camel-http/src/main/java/org/apache/camel/component/http/OAuth2ClientConfigurer.java
+++
b/components/camel-http/src/main/java/org/apache/camel/component/http/OAuth2ClientConfigurer.java
@@ -16,6 +16,14 @@
*/
package org.apache.camel.component.http;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
import org.apache.camel.util.json.DeserializationException;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
@@ -37,55 +45,143 @@ public class OAuth2ClientConfigurer implements
HttpClientConfigurer {
private final String clientSecret;
private final String tokenEndpoint;
private final String scope;
+ private final boolean cacheTokens;
+ private final Long cachedTokensDefaultExpirySeconds;
+ private final Long cachedTokensExpirationMarginSeconds;
+ private final static Map<OAuth2URIAndCredentials, TokenCache> tokenCache =
new HashMap<>();
- public OAuth2ClientConfigurer(String clientId, String clientSecret, String
tokenEndpoint, String scope) {
+ public OAuth2ClientConfigurer(String clientId, String clientSecret, String
tokenEndpoint, String scope, boolean cacheTokens,
+ long cachedTokensDefaultExpirySeconds, long
cachedTokensExpirationMarginSeconds) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tokenEndpoint = tokenEndpoint;
this.scope = scope;
+ this.cacheTokens = cacheTokens;
+ this.cachedTokensDefaultExpirySeconds =
cachedTokensDefaultExpirySeconds;
+ this.cachedTokensExpirationMarginSeconds =
cachedTokensExpirationMarginSeconds;
}
@Override
public void configureHttpClient(HttpClientBuilder clientBuilder) {
HttpClient httpClient = clientBuilder.build();
clientBuilder.addRequestInterceptorFirst((HttpRequest request,
EntityDetails entity, HttpContext context) -> {
+ URI requestUri = getUriFromRequest(request);
+ OAuth2URIAndCredentials uriAndCredentials = new
OAuth2URIAndCredentials(requestUri, clientId, clientSecret);
+ if (cacheTokens) {
+ if (tokenCache.containsKey(uriAndCredentials)
+ &&
!tokenCache.get(uriAndCredentials).isExpiredWithMargin(cachedTokensExpirationMarginSeconds))
{
+ request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " +
tokenCache.get(uriAndCredentials).getToken());
+ } else {
+ JsonObject accessTokenResponse =
getAccessTokenResponse(httpClient);
+ String accessToken =
accessTokenResponse.getString("access_token");
+ String expiresIn =
accessTokenResponse.getString("expires_in");
+ if (expiresIn != null && !expiresIn.isEmpty()) {
+ tokenCache.put(uriAndCredentials, new
TokenCache(accessToken, expiresIn));
+ } else if (cachedTokensDefaultExpirySeconds > 0) {
+ tokenCache.put(uriAndCredentials, new
TokenCache(accessToken, cachedTokensDefaultExpirySeconds));
+ }
+ request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " +
accessToken);
+ }
+ } else {
+ JsonObject accessTokenResponse =
getAccessTokenResponse(httpClient);
+ String accessToken =
accessTokenResponse.getString("access_token");
+ request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " +
accessToken);
+ }
+ });
+ }
- String url = tokenEndpoint;
- if (scope != null) {
- String sep = "?";
- if (url.contains("?")) {
- sep = "&";
+ private JsonObject getAccessTokenResponse(HttpClient httpClient) throws
IOException {
+ String url = tokenEndpoint;
+ if (scope != null) {
+ String sep = "?";
+ if (url.contains("?")) {
+ sep = "&";
+ }
+ url = url + sep + "scope=" + scope;
+ }
+
+ final HttpPost httpPost = new HttpPost(url);
+
+ httpPost.addHeader(HttpHeaders.AUTHORIZATION,
+ HttpCredentialsHelper.generateBasicAuthHeader(clientId,
clientSecret));
+ httpPost.setEntity(new StringEntity("grant_type=client_credentials",
ContentType.APPLICATION_FORM_URLENCODED));
+
+ AtomicReference<JsonObject> result = new AtomicReference<>();
+ httpClient.execute(httpPost, response -> {
+ try {
+ String responseString =
EntityUtils.toString(response.getEntity());
+
+ if (response.getCode() == 200) {
+ result.set((JsonObject)
Jsoner.deserialize(responseString));
+ } else {
+ throw new HttpException(
+ "Received error response from token request with
Status Code: " + response.getCode());
}
- url = url + sep + "scope=" + scope;
+ } catch (DeserializationException e) {
+ throw new HttpException("Something went wrong when reading
token request response", e);
}
+ return null;
+ });
+ return result.get();
+ }
- final HttpPost httpPost = new HttpPost(url);
+ private URI getUriFromRequest(HttpRequest request) {
+ URI result;
+ try {
+ result = request.getUri();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ return result;
+ }
- httpPost.addHeader(HttpHeaders.AUTHORIZATION,
- HttpCredentialsHelper.generateBasicAuthHeader(clientId,
clientSecret));
- httpPost.setEntity(new
StringEntity("grant_type=client_credentials",
ContentType.APPLICATION_FORM_URLENCODED));
+ private static class TokenCache {
+ private String token;
+ private Instant expirationTime;
- httpClient.execute(httpPost, response -> {
+ public TokenCache() {
+ }
- try {
- String responseString =
EntityUtils.toString(response.getEntity());
+ public TokenCache(String token, String expires_in) {
+ this.token = token;
+ setExpirationTimeSeconds(expires_in);
+ }
- if (response.getCode() == 200) {
- String accessToken = ((JsonObject)
Jsoner.deserialize(responseString)).getString("access_token");
- request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer "
+ accessToken);
- } else {
- throw new HttpException(
- "Received error response from token request
with Status Code: " + response.getCode());
- }
+ public TokenCache(String accessToken, Long seconds) {
+ this.token = accessToken;
+ this.expirationTime = Instant.now().plusSeconds(seconds);
+ }
- } catch (DeserializationException e) {
- throw new HttpException("Something went wrong when reading
token request response", e);
- }
+ public boolean isExpired() {
+ return Instant.now().isAfter(expirationTime);
+ }
- return null;
- });
+ public boolean isExpiredWithMargin(Long marginSeconds) {
+ return
Instant.now().isAfter(expirationTime.minusSeconds(marginSeconds));
+ }
- });
+ public void setExpirationTimeSeconds(String expires_in) {
+ this.expirationTime =
Instant.now().plusSeconds(Long.parseLong(expires_in));
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public Instant getExpirationTime() {
+ return expirationTime;
+ }
+
+ public void setExpirationTime(Instant expirationTime) {
+ this.expirationTime = expirationTime;
+ }
+ }
+
+ private record OAuth2URIAndCredentials(URI uri, String clientId, String
clientSecret) {
}
}
diff --git
a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpOAuth2TokenCachingTest.java
b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpOAuth2TokenCachingTest.java
new file mode 100644
index 00000000000..fb8cd4c95c2
--- /dev/null
+++
b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpOAuth2TokenCachingTest.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.camel.component.http;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.http.handler.HeaderValidationHandler;
+import org.apache.camel.component.http.handler.OAuth2TokenRequestHandler;
+import org.apache.hc.client5.http.HttpHostConnectException;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
+import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class HttpOAuth2TokenCachingTest extends BaseHttpTest {
+
+ private static final String FAKE_TOKEN = "xxx.yyy.zzz";
+ private static final String clientId = "test-client";
+ private static final String clientSecret = "test-secret";
+ private static final OAuth2TokenRequestHandler handler = new
OAuth2TokenRequestHandler(FAKE_TOKEN, clientId, clientSecret);
+
+ @Override
+ public void setupResources() throws Exception {
+ }
+
+ @Test
+ public void tokenIsCached() throws Exception {
+ try (var localServer = createLocalServer(); var localOAuth2Server =
createLocalOAuth2Server()) {
+ String tokenEndpoint = "http://localhost:" +
localOAuth2Server.getLocalPort() + "/token";
+ String requestUrl = "http://localhost:" +
localServer.getLocalPort() + "/post?httpMethod=POST&oauth2ClientId="
+ + clientId + "&oauth2ClientSecret=" +
clientSecret + "&oauth2TokenEndpoint=" + tokenEndpoint +
+ "&oauth2CacheTokens=" + true;
+
+ template.request(requestUrl,
+ exchange1 -> {
+ });
+ localOAuth2Server.close();
+ Exchange exchange
+ = template.request(requestUrl,
+ exchange1 -> {
+ });
+ assertExchange(exchange);
+ }
+ }
+
+ @Test
+ public void tokenIsNotCachedWhenCacheTokensIsFalse() throws Exception {
+ try (var localServer = createLocalServer(); var localOAuth2Server =
createLocalOAuth2Server()) {
+ String tokenEndpoint = "http://localhost:" +
localOAuth2Server.getLocalPort() + "/token";
+ String requestUrl = "http://localhost:" +
localServer.getLocalPort() + "/post?httpMethod=POST&oauth2ClientId="
+ + clientId + "&oauth2ClientSecret=" +
clientSecret + "&oauth2TokenEndpoint=" + tokenEndpoint +
+ "&oauth2CacheTokens=" + false;
+
+ template.request(requestUrl,
+ exchange1 -> {
+ });
+ localOAuth2Server.close();
+ Exchange exchange
+ = template.request(requestUrl,
+ exchange1 -> {
+ });
+ assertExceptionExchange(exchange);
+ }
+ }
+
+ @Test
+ public void toDTokenIsCached() throws Exception {
+ try (var localServer = createLocalServer(); var localOAuth2Server =
createLocalOAuth2Server()) {
+ String tokenEndpoint = "http://localhost:" +
localOAuth2Server.getLocalPort() + "/token";
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .setVariable("cid", constant(clientId))
+ .setVariable("cs", constant(clientSecret))
+ .toD("http://localhost:" +
localServer.getLocalPort()
+ +
"/post?httpMethod=POST&oauth2ClientId=${variable.cid}"
+ +
"&oauth2ClientSecret=${variable:cs}&oauth2TokenEndpoint=" + tokenEndpoint
+ + "&oauth2CacheTokens=" + true);
+ }
+ });
+
+ template.send("direct:start", e -> {
+ });
+ localOAuth2Server.close();
+ Exchange exchange = template.send("direct:start", e -> {
+ });
+
+ assertExchange(exchange);
+ }
+ }
+
+ @Test
+ public void toDTokenIsNotCachedWhenCacheTokensIsFalse() throws Exception {
+ try (var localServer = createLocalServer(); var localOAuth2Server =
createLocalOAuth2Server()) {
+ String tokenEndpoint = "http://localhost:" +
localOAuth2Server.getLocalPort() + "/token";
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .setVariable("cid", constant(clientId))
+ .setVariable("cs", constant(clientSecret))
+ .toD("http://localhost:" +
localServer.getLocalPort()
+ +
"/post?httpMethod=POST&oauth2ClientId=${variable.cid}"
+ +
"&oauth2ClientSecret=${variable:cs}&oauth2TokenEndpoint=" + tokenEndpoint
+ + "&oauth2CacheTokens=" + false);
+ }
+ });
+
+ template.send("direct:start", e -> {
+ });
+ localOAuth2Server.close();
+ Exchange exchange = template.send("direct:start", e -> {
+ });
+
+ assertExceptionExchange(exchange);
+ }
+ }
+
+ protected void assertExceptionExchange(Exchange exchange) {
+ assertNotNull(exchange);
+ assertNotNull(exchange.getException());
+ Exception exception = exchange.getException();
+ assertEquals(HttpHostConnectException.class, exception.getClass());
+ }
+
+ protected void assertHeaders(Map<String, Object> headers) {
+ assertEquals(HttpStatus.SC_OK,
headers.get(Exchange.HTTP_RESPONSE_CODE));
+ }
+
+ protected String getExpectedContent() {
+ return "";
+ }
+
+ private HttpServer createLocalServer() throws Exception {
+ Map<String, String> expectedHeaders = new HashMap<>();
+ expectedHeaders.put("Authorization", "Bearer " + FAKE_TOKEN);
+
+ var localServer = ServerBootstrap.bootstrap()
+
.setCanonicalHostName("localhost").setHttpProcessor(getBasicHttpProcessor())
+
.setConnectionReuseStrategy(getConnectionReuseStrategy()).setResponseFactory(getHttpResponseFactory())
+ .setSslContext(getSSLContext())
+ .register("/post",
+ new HeaderValidationHandler(
+ "POST",
+ null,
+ null,
+ null,
+ expectedHeaders))
+ .create();
+
+ localServer.start();
+ return localServer;
+ }
+
+ private HttpServer createLocalOAuth2Server() throws Exception {
+ var localOAuth2Server = ServerBootstrap.bootstrap()
+
.setCanonicalHostName("localhost").setHttpProcessor(getBasicHttpProcessor())
+
.setConnectionReuseStrategy(getConnectionReuseStrategy()).setResponseFactory(getHttpResponseFactory())
+ .setSslContext(getSSLContext())
+ .register("/token", handler)
+ .create();
+
+ localOAuth2Server.start();
+ return localOAuth2Server;
+ }
+}
diff --git
a/components/camel-http/src/test/java/org/apache/camel/component/http/handler/OAuth2TokenRequestHandler.java
b/components/camel-http/src/test/java/org/apache/camel/component/http/handler/OAuth2TokenRequestHandler.java
index aa2253cae0a..cb649223f79 100644
---
a/components/camel-http/src/test/java/org/apache/camel/component/http/handler/OAuth2TokenRequestHandler.java
+++
b/components/camel-http/src/test/java/org/apache/camel/component/http/handler/OAuth2TokenRequestHandler.java
@@ -49,7 +49,6 @@ public class OAuth2TokenRequestHandler implements
HttpRequestHandler {
@Override
public void handle(ClassicHttpRequest request, ClassicHttpResponse
response, HttpContext context)
throws HttpException, IOException {
-
String requestBody = EntityUtils.toString(request.getEntity());
WWWFormCodec.parse(requestBody, StandardCharsets.UTF_8).stream()
.filter(pair -> pair.getName().equals("grant_type") &&
pair.getValue().equals("client_credentials"))
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/HttpEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/HttpEndpointBuilderFactory.java
index deccba69442..be465a3e345 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/HttpEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/HttpEndpointBuilderFactory.java
@@ -596,6 +596,112 @@ public interface HttpEndpointBuilderFactory {
doSetProperty("authUsername", authUsername);
return this;
}
+ /**
+ * Default expiration time for cached OAuth2 tokens, in seconds. Used
if
+ * token response does not contain 'expires_in' field.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Default: 3600
+ * Group: security
+ *
+ * @param oauth2CachedTokensDefaultExpirySeconds the value to set
+ * @return the dsl builder
+ */
+ default HttpEndpointBuilder
oauth2CachedTokensDefaultExpirySeconds(long
oauth2CachedTokensDefaultExpirySeconds) {
+ doSetProperty("oauth2CachedTokensDefaultExpirySeconds",
oauth2CachedTokensDefaultExpirySeconds);
+ return this;
+ }
+ /**
+ * Default expiration time for cached OAuth2 tokens, in seconds. Used
if
+ * token response does not contain 'expires_in' field.
+ *
+ * The option will be converted to a <code>long</code> type.
+ *
+ * Default: 3600
+ * Group: security
+ *
+ * @param oauth2CachedTokensDefaultExpirySeconds the value to set
+ * @return the dsl builder
+ */
+ default HttpEndpointBuilder
oauth2CachedTokensDefaultExpirySeconds(String
oauth2CachedTokensDefaultExpirySeconds) {
+ doSetProperty("oauth2CachedTokensDefaultExpirySeconds",
oauth2CachedTokensDefaultExpirySeconds);
+ return this;
+ }
+ /**
+ * Amount of time which is deducted from OAuth2 tokens expiry time to
+ * compensate for the time it takes OAuth2 Token Endpoint to send the
+ * token over http, in seconds. Set this parameter to high value if you
+ * OAuth2 Token Endpoint answers slowly or you tokens expire quickly.
If
+ * you set this parameter to too small value, you can get 4xx http
+ * errors because camel will think that the received token is still
+ * valid, while in reality the token is expired for the Authentication
+ * server.
+ *
+ * The option is a: <code>long</code> type.
+ *
+ * Default: 5
+ * Group: security
+ *
+ * @param oauth2CachedTokensExpirationMarginSeconds the value to set
+ * @return the dsl builder
+ */
+ default HttpEndpointBuilder
oauth2CachedTokensExpirationMarginSeconds(long
oauth2CachedTokensExpirationMarginSeconds) {
+ doSetProperty("oauth2CachedTokensExpirationMarginSeconds",
oauth2CachedTokensExpirationMarginSeconds);
+ return this;
+ }
+ /**
+ * Amount of time which is deducted from OAuth2 tokens expiry time to
+ * compensate for the time it takes OAuth2 Token Endpoint to send the
+ * token over http, in seconds. Set this parameter to high value if you
+ * OAuth2 Token Endpoint answers slowly or you tokens expire quickly.
If
+ * you set this parameter to too small value, you can get 4xx http
+ * errors because camel will think that the received token is still
+ * valid, while in reality the token is expired for the Authentication
+ * server.
+ *
+ * The option will be converted to a <code>long</code> type.
+ *
+ * Default: 5
+ * Group: security
+ *
+ * @param oauth2CachedTokensExpirationMarginSeconds the value to set
+ * @return the dsl builder
+ */
+ default HttpEndpointBuilder
oauth2CachedTokensExpirationMarginSeconds(String
oauth2CachedTokensExpirationMarginSeconds) {
+ doSetProperty("oauth2CachedTokensExpirationMarginSeconds",
oauth2CachedTokensExpirationMarginSeconds);
+ return this;
+ }
+ /**
+ * Whether to cache OAuth2 client tokens.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param oauth2CacheTokens the value to set
+ * @return the dsl builder
+ */
+ default HttpEndpointBuilder oauth2CacheTokens(boolean
oauth2CacheTokens) {
+ doSetProperty("oauth2CacheTokens", oauth2CacheTokens);
+ return this;
+ }
+ /**
+ * Whether to cache OAuth2 client tokens.
+ *
+ * The option will be converted to a <code>boolean</code> type.
+ *
+ * Default: false
+ * Group: security
+ *
+ * @param oauth2CacheTokens the value to set
+ * @return the dsl builder
+ */
+ default HttpEndpointBuilder oauth2CacheTokens(String
oauth2CacheTokens) {
+ doSetProperty("oauth2CacheTokens", oauth2CacheTokens);
+ return this;
+ }
/**
* OAuth2 client id.
*