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 <ondrusek.j...@gmail.com>
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>

Reply via email to