This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push:
new e712d63 CAMEL-14977: Create camel-undertow-spring-security component
(#3782)
e712d63 is described below
commit e712d63478a8ff456a3543596cf43f8cf4107262
Author: JiriOndrusek <[email protected]>
AuthorDate: Tue Apr 28 18:59:20 2020 +0200
CAMEL-14977: Create camel-undertow-spring-security component (#3782)
---
apache-camel/src/main/descriptors/common-bin.xml | 1 +
components/camel-undertow-spring-security/pom.xml | 88 +++++++++++++++
.../docs/undertow-spring-security-component.adoc | 32 ++++++
.../security/SpringSecurityConfiguration.java | 29 +++++
.../spring/security/SpringSecurityProvider.java | 100 +++++++++++++++++
.../KeycloakJwtAuthenticationConverter.java | 29 +++++
.../keycloak/KeycloakRealmRoleConverter.java | 44 ++++++++
.../keycloak/KeycloakUsernameSubClaimAdapter.java | 46 ++++++++
...component.undertow.spi.UndertowSecurityProvider | 1 +
.../AbstractSpringSecurityBearerTokenTest.java | 122 +++++++++++++++++++++
.../component/spring/security/MockFilter.java | 68 ++++++++++++
.../security/SpringSecurityBearerTokenTest.java | 66 +++++++++++
.../src/test/resources/log4j2.properties | 29 +++++
.../src/main/docs/undertow-component.adoc | 4 +
.../component/undertow/DefaultUndertowHost.java | 11 +-
components/pom.xml | 1 +
docs/components/modules/ROOT/nav.adoc | 1 +
.../modules/ROOT/pages/undertow-component.adoc | 4 +
.../pages/undertow-spring-security-component.adoc | 34 ++++++
parent/pom.xml | 5 +
20 files changed, 712 insertions(+), 3 deletions(-)
diff --git a/apache-camel/src/main/descriptors/common-bin.xml
b/apache-camel/src/main/descriptors/common-bin.xml
index c2efcc3..823fda0 100644
--- a/apache-camel/src/main/descriptors/common-bin.xml
+++ b/apache-camel/src/main/descriptors/common-bin.xml
@@ -366,6 +366,7 @@
<include>org.apache.camel:camel-twilio</include>
<include>org.apache.camel:camel-twitter</include>
<include>org.apache.camel:camel-undertow</include>
+ <include>org.apache.camel:camel-undertow-spring-security</include>
<include>org.apache.camel:camel-univocity-parsers</include>
<include>org.apache.camel:camel-validator</include>
<include>org.apache.camel:camel-velocity</include>
diff --git a/components/camel-undertow-spring-security/pom.xml
b/components/camel-undertow-spring-security/pom.xml
new file mode 100644
index 0000000..23a9010
--- /dev/null
+++ b/components/camel-undertow-spring-security/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>components</artifactId>
+ <version>3.3.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>camel-undertow-spring-security</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Camel :: Undertow Spring Security</name>
+ <description>Spring Security Provider for camel-undertow</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-undertow</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-oauth2-client</artifactId>
+ <version>${spring-security-version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-oauth2-jose</artifactId>
+ <version>${spring-security-version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-oauth2-resource-server</artifactId>
+ <version>${spring-security-version}</version>
+ </dependency>
+
+ <!-- testing -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <version>${nimbus-jose-jwt}</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-package-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate</id>
+ <phase>none</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+
+</project>
diff --git
a/components/camel-undertow-spring-security/src/main/docs/undertow-spring-security-component.adoc
b/components/camel-undertow-spring-security/src/main/docs/undertow-spring-security-component.adoc
new file mode 100644
index 0000000..e2f8318
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/main/docs/undertow-spring-security-component.adoc
@@ -0,0 +1,32 @@
+[[undertow-spring-security-component]]
+= Undertow Spring Security Security Provider
+//by hand
+:since: 3.3
+
+*Since Camel {since}*
+
+*OSGi is not supported*
+
+
+The Spring Security Provider provides Spring Security (5.x) token bearer
security over camel-undertow component.
+To force camel-undertow to use spring security provider:
+
+* Add spring security provider library on classpath.
+* Provide instance of SpringSecurityConfiguration as `securityConfiguration`
+parameter into camel-undertow component or provide both
`securityConfiguration` and `securityProvider`
+into camel-undertow component.
+* Configure spring-security.
+
+Configuration has to provide following security attribute:
+[width="100%"]
+|===
+| Name | Description | Type
+| *securityFiler* | Provides security filter gained from configured spring
security (5.x). Filter could be obtained
+for example from DelegatingFilterProxyRegistrationBean. | Filter
+|===
+
+Each exchange created by Undertow endpoint with spring security contains
header 'SpringSecurityProvider_principal' (
+name of header is provided as a constant
`SpringSecurityProvider.PRINCIPAL_NAME_HEADER`) with current authorized identity
+as value or header is not present in case of rejected requests.
+
+
diff --git
a/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/SpringSecurityConfiguration.java
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/SpringSecurityConfiguration.java
new file mode 100644
index 0000000..72cc891
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/SpringSecurityConfiguration.java
@@ -0,0 +1,29 @@
+/*
+ * 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.spring.security;
+
+import javax.servlet.Filter;
+
+public interface SpringSecurityConfiguration {
+
+ /**
+ * Provides security filter gained from configured spring security (5+).
+ * Filter could be obtained for example from
DelegatingFilterProxyRegistrationBean.
+ */
+ Filter getSecurityFilter();
+
+}
diff --git
a/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/SpringSecurityProvider.java
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/SpringSecurityProvider.java
new file mode 100644
index 0000000..09f46f9
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/SpringSecurityProvider.java
@@ -0,0 +1,100 @@
+/*
+ * 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.spring.security;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.StatusCodes;
+import org.apache.camel.component.undertow.spi.UndertowSecurityProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+
+public class SpringSecurityProvider implements UndertowSecurityProvider {
+
+ public static final String PRINCIPAL_NAME_HEADER =
SpringSecurityProvider.class.getName() + "_principal";
+ private static final AttachmentKey<String> PRINCIPAL_NAME_KEY =
AttachmentKey.create(String.class);
+
+ private Filter securityFilter;
+
+ @Override
+ public void addHeader(BiConsumer<String, Object> consumer,
HttpServerExchange httpExchange) throws Exception {
+ String principalName = httpExchange.getAttachment(PRINCIPAL_NAME_KEY);
+ consumer.accept(PRINCIPAL_NAME_HEADER, principalName);
+ }
+
+ @Override
+ public int authenticate(HttpServerExchange httpExchange, List<String>
allowedRoles) throws Exception {
+ ServletRequestContext servletRequestContext =
httpExchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ ServletRequest request = servletRequestContext.getServletRequest();
+ ServletResponse response = servletRequestContext.getServletResponse();
+
+ //new filter has to be added into the filter chain. If is successfully
called it means that security allows access.
+ FilterChain fc = (servletRequest, servletResponse) -> {
+ Authentication a =
SecurityContextHolder.getContext().getAuthentication();
+ if (a instanceof JwtAuthenticationToken) {
+ boolean allowed = false;
+ Collection<GrantedAuthority> grantedAuthorities =
((JwtAuthenticationToken) a).getAuthorities();
+ for (GrantedAuthority grantedAuthority : grantedAuthorities) {
+ if
(allowedRoles.contains(grantedAuthority.getAuthority())) {
+ allowed = true;
+ break;
+ }
+ }
+
+ if (allowed) {
+ httpExchange.putAttachment(PRINCIPAL_NAME_KEY,
((JwtAuthenticationToken) a).getName());
+ httpExchange.setStatusCode(StatusCodes.OK);
+ return;
+ }
+
+ httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
+ }
+ };
+ securityFilter.doFilter(request, response, fc);
+
+ return httpExchange.getStatusCode();
+ }
+
+
+ @Override
+ public boolean acceptConfiguration(Object configuration, String
endpointUri) throws Exception {
+ if (configuration instanceof SpringSecurityConfiguration) {
+ SpringSecurityConfiguration conf = (SpringSecurityConfiguration)
configuration;
+ this.securityFilter = conf.getSecurityFilter();
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean requireServletContext() {
+ return true;
+ }
+}
diff --git
a/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakJwtAuthenticationConverter.java
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakJwtAuthenticationConverter.java
new file mode 100644
index 0000000..b2823aa
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakJwtAuthenticationConverter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.spring.security.keycloak;
+
+import
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
+
+/**
+ * JwtAuthentication converter prepared with KeycloakRealmRoleConverter.
+ */
+public class KeycloakJwtAuthenticationConverter extends
JwtAuthenticationConverter {
+
+ public KeycloakJwtAuthenticationConverter() {
+ setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter());
+ }
+}
diff --git
a/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakRealmRoleConverter.java
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakRealmRoleConverter.java
new file mode 100644
index 0000000..8dfd2be
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakRealmRoleConverter.java
@@ -0,0 +1,44 @@
+/*
+ * 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.spring.security.keycloak;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+/**
+ * Converts JWT token into list of roles.
+ */
+public class KeycloakRealmRoleConverter implements Converter<Jwt,
Collection<GrantedAuthority>> {
+
+ public static final String REALM_ACCESS = "realm_access";
+ public static final String ROLES = "roles";
+
+ @Override
+ public Collection<GrantedAuthority> convert(final Jwt jwt) {
+ final Map<String, Object> realmAccess = (Map<String, Object>)
jwt.getClaims().get(REALM_ACCESS);
+ return ((List<String>)realmAccess.get(ROLES)).stream()
+ .map(SimpleGrantedAuthority::new)
+ .collect(Collectors.toList());
+ }
+}
diff --git
a/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakUsernameSubClaimAdapter.java
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakUsernameSubClaimAdapter.java
new file mode 100644
index 0000000..05a284d
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/main/java/org/apache/camel/component/spring/security/keycloak/KeycloakUsernameSubClaimAdapter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.spring.security.keycloak;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter;
+
+/**
+ * See
https://docs.spring.io/spring-security/site/docs/5.2.x/reference/html5/#oauth2resourceserver-jwt-claimsetmapping-rename
+ * for more information.
+ */
+public class KeycloakUsernameSubClaimAdapter implements Converter<Map<String,
Object>, Map<String, Object>> {
+
+ private final MappedJwtClaimSetConverter delegate =
MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
+
+ private final String userNameAttribute;
+
+ public KeycloakUsernameSubClaimAdapter(String userNameAttribute) {
+ this.userNameAttribute = userNameAttribute;
+ }
+
+ @Override
+ public Map<String, Object> convert(Map<String, Object> claims) {
+ Map<String, Object> convertedClaims = this.delegate.convert(claims);
+ String username = (String) convertedClaims.get(userNameAttribute);
+ convertedClaims.put("sub", username);
+ return convertedClaims;
+ }
+}
\ No newline at end of file
diff --git
a/components/camel-undertow-spring-security/src/main/resources/META-INF/services/org.apache.camel.component.undertow.spi.UndertowSecurityProvider
b/components/camel-undertow-spring-security/src/main/resources/META-INF/services/org.apache.camel.component.undertow.spi.UndertowSecurityProvider
new file mode 100644
index 0000000..d185902
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/main/resources/META-INF/services/org.apache.camel.component.undertow.spi.UndertowSecurityProvider
@@ -0,0 +1 @@
+org.apache.camel.component.spring.security.SpringSecurityProvider
\ No newline at end of file
diff --git
a/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/AbstractSpringSecurityBearerTokenTest.java
b/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/AbstractSpringSecurityBearerTokenTest.java
new file mode 100644
index 0000000..c233852
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/AbstractSpringSecurityBearerTokenTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.spring.security;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.Filter;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.CamelContext;
+import
org.apache.camel.component.spring.security.keycloak.KeycloakRealmRoleConverter;
+import
org.apache.camel.component.spring.security.keycloak.KeycloakUsernameSubClaimAdapter;
+import org.apache.camel.component.undertow.UndertowComponent;
+import org.apache.camel.component.undertow.spi.UndertowSecurityProvider;
+import org.apache.camel.test.AvailablePortFinder;
+import org.apache.camel.test.junit4.CamelTestSupport;
+import org.junit.BeforeClass;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+public abstract class AbstractSpringSecurityBearerTokenTest extends
CamelTestSupport {
+
+ private static volatile int port;
+
+ private final MockFilter mockFilter = new MockFilter();
+
+ public MockFilter getMockFilter() {
+ return mockFilter;
+ }
+
+ @BeforeClass
+ public static void initPort() throws Exception {
+ port = AvailablePortFinder.getNextAvailable();
+
+ URL location =
SpringSecurityProvider.class.getProtectionDomain().getCodeSource().getLocation();
+ File file = new File(location.getPath() + "META-INF/services/" +
UndertowSecurityProvider.class.getName());
+ file.getParentFile().mkdirs();
+
+ Writer output = new FileWriter(file);
+ output.write(SpringSecurityProvider.class.getName());
+ output.close();
+
+ file.deleteOnExit();
+ }
+
+ protected static int getPort() {
+ return port;
+ }
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+
+ context.getPropertiesComponent().setLocation("ref:prop");
+
+ context.getComponent("undertow",
UndertowComponent.class).setSecurityConfiguration(new
SpringSecurityConfiguration() {
+ @Override
+ public Filter getSecurityFilter() {
+ return mockFilter;
+ }
+ });
+
+ return context;
+ }
+
+ @BindToRegistry("prop")
+ public Properties loadProperties() throws Exception {
+
+ Properties prop = new Properties();
+ prop.setProperty("port", "" + getPort());
+ return prop;
+ }
+
+ Jwt createToken(String userName, String role) {
+ JWTClaimsSet.Builder claimsSet = new JWTClaimsSet.Builder();
+
+ claimsSet.subject("123445667");
+ claimsSet.claim("preffered_name", userName);
+ claimsSet.audience("resource-server");
+ claimsSet.issuer("came-spring-security");
+
+ PlainJWT plainJWT = new PlainJWT(claimsSet.build());
+
+ Map<String, Object> headers = new HashMap();
+ headers.put("type", "JWT");
+ headers.put("alg", "RS256");
+ Map<String, Object> claims = new
KeycloakUsernameSubClaimAdapter("preffered_name").convert(claimsSet.getClaims());
+
+ JSONArray roles = new JSONArray();
+ roles.appendElement(role);
+ JSONObject r = new JSONObject();
+ r.put(KeycloakRealmRoleConverter.ROLES, roles);
+ claims.put(KeycloakRealmRoleConverter.REALM_ACCESS, new JSONObject(r));
+
+ Jwt retVal = new Jwt(plainJWT.serialize(), Instant.now(),
Instant.now().plusSeconds(10), headers, claims);
+ return retVal;
+ }
+}
diff --git
a/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/MockFilter.java
b/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/MockFilter.java
new file mode 100644
index 0000000..c7ed518
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/MockFilter.java
@@ -0,0 +1,68 @@
+/*
+ * 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.spring.security;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import
org.apache.camel.component.spring.security.keycloak.KeycloakRealmRoleConverter;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.jwt.Jwt;
+import
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+
+public class MockFilter implements Filter {
+
+ private Jwt jwt;
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
+ if (jwt == null) {
+ throw new AccessDeniedException("not allowed");
+ }
+
+ Collection<? extends GrantedAuthority> grantedAuthorities = new
KeycloakRealmRoleConverter().convert(jwt);
+
+ SecurityContextHolder.getContext().setAuthentication(new
JwtAuthenticationToken(jwt, grantedAuthorities));
+
+ if (chain != null) {
+ chain.doFilter(request, response);
+ }
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+ public void setJwt(Jwt jwt) {
+ this.jwt = jwt;
+ }
+}
diff --git
a/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/SpringSecurityBearerTokenTest.java
b/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/SpringSecurityBearerTokenTest.java
new file mode 100644
index 0000000..6080d84
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/test/java/org/apache/camel/component/spring/security/SpringSecurityBearerTokenTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.spring.security;
+
+import io.undertow.util.StatusCodes;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.http.base.HttpOperationFailedException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SpringSecurityBearerTokenTest extends
AbstractSpringSecurityBearerTokenTest {
+
+ @Test
+ public void testBearerTokenAccess() throws Exception {
+ //configure token in mockFilter
+ getMockFilter().setJwt(createToken("Alice", "user"));
+
+ String response =
template.requestBody("undertow:http://localhost:{{port}}/myapp",
+ "empty body",
+ String.class);
+ assertNotNull(response);
+ assertEquals("Hello Alice!", response);
+ }
+
+ @Test
+ public void testBearerTokenForbidden() throws Exception {
+ //configure token in mockFilter
+ getMockFilter().setJwt(createToken("Tom", "wrongUser"));
+
+ try {
+ template.requestBody("undertow:http://localhost:{{port}}/myapp",
+ "empty body",
+ String.class);
+ Assert.fail("Access is denied");
+ } catch (CamelExecutionException e) {
+ HttpOperationFailedException he =
assertIsInstanceOf(HttpOperationFailedException.class, e.getCause());
+ assertEquals(StatusCodes.FORBIDDEN, he.getStatusCode());
+ }
+ }
+
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ public void configure() {
+
from("undertow:http://localhost:{{port}}/myapp?allowedRoles=user")
+ .transform(simple("Hello ${in.header." +
SpringSecurityProvider.PRINCIPAL_NAME_HEADER + "}!"));
+ }
+ };
+ }
+}
diff --git
a/components/camel-undertow-spring-security/src/test/resources/log4j2.properties
b/components/camel-undertow-spring-security/src/test/resources/log4j2.properties
new file mode 100644
index 0000000..239e564
--- /dev/null
+++
b/components/camel-undertow-spring-security/src/test/resources/log4j2.properties
@@ -0,0 +1,29 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+appender.file.type = File
+appender.file.name = file
+appender.file.fileName = target/camel-spring-security-test.log
+appender.file.layout.type = PatternLayout
+appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n
+appender.out.type = Console
+appender.out.name = out
+appender.out.layout.type = PatternLayout
+appender.out.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n
+rootLogger.level = INFO
+rootLogger.appenderRef.file.ref = file
+
diff --git a/components/camel-undertow/src/main/docs/undertow-component.adoc
b/components/camel-undertow/src/main/docs/undertow-component.adoc
index c78eb97..20db802 100644
--- a/components/camel-undertow/src/main/docs/undertow-component.adoc
+++ b/components/camel-undertow/src/main/docs/undertow-component.adoc
@@ -224,4 +224,8 @@ Java SPI (Service Provider Interfaces). If there is an
object passed to componen
as parameter `securityConfiguration` and provider accepts it. Provider will be
used
for authentication of all requests.
+Property `requireServletContext` of security providers forces udertow server
to start
+with servlet context. There will be no servlet actually handled. This feature
is meant only
+for use with servlet filters, which needs servlet context for their
functionality.
+
include::camel-spring-boot::page$undertow-starter.adoc[]
diff --git
a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHost.java
b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHost.java
index 5defbc9..1638cc8 100644
---
a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHost.java
+++
b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHost.java
@@ -45,6 +45,7 @@ public class DefaultUndertowHost implements UndertowHost {
private final RestRootHandler restHandler;
private Undertow undertow;
private String hostString;
+ private DeploymentManager deploymentManager;
public DefaultUndertowHost(UndertowHostKey key) {
this(key, null);
@@ -139,15 +140,16 @@ public class DefaultUndertowHost implements UndertowHost {
//httpHandler for servlet is ignored, camel handler is
used instead of it
.addOuterHandlerChainWrapper(h -> handler);
- DeploymentManager manager =
Servlets.newContainer().addDeployment(deployment);
- manager.deploy();
+ deploymentManager =
Servlets.newContainer().addDeployment(deployment);
+ deploymentManager.deploy();
try {
- return builder.setHandler(manager.start()).build();
+ return builder.setHandler(deploymentManager.start()).build();
} catch (ServletException e) {
LOG.warn("Failed to start Undertow server on {}://{}:{},
reason: {}", key.getSslContext() != null ? "https" : "http", key.getHost(),
key.getPort(), e.getMessage());
throw new RuntimeException(e);
}
+
}
return builder.setHandler(handler).build();
@@ -167,6 +169,9 @@ public class DefaultUndertowHost implements UndertowHost {
rootHandler.remove(registrationInfo.getUri().getPath(),
registrationInfo.getMethodRestrict(), registrationInfo.isMatchOnUriPrefix());
stop = rootHandler.isEmpty();
}
+ if (deploymentManager != null) {
+ deploymentManager.undeploy();
+ }
if (stop) {
LOG.info("Stopping Undertow server on {}://{}:{}",
key.getSslContext() != null ? "https" : "http", key.getHost(), key.getPort());
diff --git a/components/pom.xml b/components/pom.xml
index b4e4923a..bfe1b68 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -355,6 +355,7 @@
<module>camel-tika</module>
<module>camel-twilio</module>
<module>camel-twitter</module>
+ <module>camel-undertow-spring-security</module>
<module>camel-univocity-parsers</module>
<module>camel-velocity</module>
<module>camel-vertx</module>
diff --git a/docs/components/modules/ROOT/nav.adoc
b/docs/components/modules/ROOT/nav.adoc
index 99799e9..b67dbf9 100644
--- a/docs/components/modules/ROOT/nav.adoc
+++ b/docs/components/modules/ROOT/nav.adoc
@@ -320,6 +320,7 @@
** xref:twitter-timeline-component.adoc[Twitter Timeline]
** xref:undertow-component.adoc[Undertow]
** xref:elytron-component.adoc[Undertow Elytron Security Provider]
+** xref:undertow-spring-security-component.adoc[Undertow Spring Security
Security Provider]
** xref:validator-component.adoc[Validator]
** xref:velocity-component.adoc[Velocity]
** xref:vertx-component.adoc[Vert.x]
diff --git a/docs/components/modules/ROOT/pages/undertow-component.adoc
b/docs/components/modules/ROOT/pages/undertow-component.adoc
index a74cfc6..282c603 100644
--- a/docs/components/modules/ROOT/pages/undertow-component.adoc
+++ b/docs/components/modules/ROOT/pages/undertow-component.adoc
@@ -226,4 +226,8 @@ Java SPI (Service Provider Interfaces). If there is an
object passed to componen
as parameter `securityConfiguration` and provider accepts it. Provider will be
used
for authentication of all requests.
+Property `requireServletContext` of security providers forces udertow server
to start
+with servlet context. There will be no servlet actually handled. This feature
is meant only
+for use with servlet filters, which needs servlet context for their
functionality.
+
include::camel-spring-boot::page$undertow-starter.adoc[]
diff --git
a/docs/components/modules/ROOT/pages/undertow-spring-security-component.adoc
b/docs/components/modules/ROOT/pages/undertow-spring-security-component.adoc
new file mode 100644
index 0000000..1e1b7e0
--- /dev/null
+++ b/docs/components/modules/ROOT/pages/undertow-spring-security-component.adoc
@@ -0,0 +1,34 @@
+[[undertow-spring-security-component]]
+= Undertow Spring Security Security Provider
+//THIS FILE IS COPIED: EDIT THE SOURCE FILE:
+:page-source:
components/camel-undertow-spring-security/src/main/docs/undertow-spring-security-component.adoc
+//by hand
+:since: 3.2
+
+*Since Camel {since}*
+
+*OSGi is not supported*
+
+The Spring Security Provider provides Spring Security (5+) token bearer
security over camel-undertow component.
+To force camel-undertow to use spring security provider:
+- Add spring security provider library on classpath.
+- Provide instance of SpringSecurityConfiguration as `securityConfiguration`
+parameter into camel-undertow component or provide both
`securityConfiguration` and `securityProvider`
+into camel-undertow component.
+- Configure spring-security.
+
+Configuration has to provide all 3 security attributes:
+[width="100%"]
+|===
+| Name | Description | Type
+| *securityFiler* | Provides security filter gained from configured spring
security (5+). Filter could be obtained
+for example from DelegatingFilterProxyRegistrationBean. | Filter
+| *clieantRegistration* | Provides configuration of external security provider
which would be used by spring security |
+ClientRegistration
+|===
+
+Each exchange created by Undertow endpoint with spring security contains
header 'SpringSecurityProvider_principal' (
+name of header is provided as constant
`SpringSecurityProvider.PRINCIPAL_NAME_HEADER`) with current authorized identity
+as value or is not present (for not authorized requests)
+
+
diff --git a/parent/pom.xml b/parent/pom.xml
index 4778c68..12f57e0 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -2368,6 +2368,11 @@
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
+ <artifactId>camel-undertow-spring-security</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
<artifactId>camel-univocity-parsers</artifactId>
<version>${project.version}</version>
</dependency>