diff --git a/distro/run/assembly/pom.xml b/distro/run/assembly/pom.xml
index 6163b80252..9a63326ee4 100644
--- a/distro/run/assembly/pom.xml
+++ b/distro/run/assembly/pom.xml
@@ -54,7 +54,7 @@
org.operaton.bpm.run
operaton-bpm-run-modules-oauth2
${project.version}
- jar
+ pom
diff --git a/distro/run/modules/oauth2/pom.xml b/distro/run/modules/oauth2/pom.xml
index 21c13db8fb..833b8203bb 100644
--- a/distro/run/modules/oauth2/pom.xml
+++ b/distro/run/modules/oauth2/pom.xml
@@ -11,7 +11,7 @@
operaton-bpm-run-modules-oauth2
Operaton Platform - Run - Module Spring Security
- jar
+ pom
@@ -31,12 +43,6 @@
spring-boot-starter-oauth2-client
-
- ${project.groupId}
- operaton-bpm-spring-boot-starter-rest
- ${project.version}
- test
-
operaton-bpm-spring-boot-starter-test
${project.groupId}
diff --git a/distro/run/modules/oauth2/src/main/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSpringSecurityDisableAutoConfiguration.java b/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/OperatonBpmSpringSecurityDisableAutoConfiguration.java
similarity index 86%
rename from distro/run/modules/oauth2/src/main/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSpringSecurityDisableAutoConfiguration.java
rename to spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/OperatonBpmSpringSecurityDisableAutoConfiguration.java
index 53d0ace690..1578a02c87 100644
--- a/distro/run/modules/oauth2/src/main/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSpringSecurityDisableAutoConfiguration.java
+++ b/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/OperatonBpmSpringSecurityDisableAutoConfiguration.java
@@ -14,15 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.operaton.bpm.spring.boot.starter.security;
+package org.operaton.bpm.spring.boot.starter.security.oauth2;
+import org.operaton.bpm.spring.boot.starter.security.oauth2.impl.ClientsNotConfiguredCondition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
+@Conditional(ClientsNotConfiguredCondition.class)
public class OperatonBpmSpringSecurityDisableAutoConfiguration {
private static final Logger logger = LoggerFactory.getLogger(OperatonBpmSpringSecurityDisableAutoConfiguration.class);
diff --git a/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/OperatonSpringSecurityOAuth2AutoConfiguration.java b/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/OperatonSpringSecurityOAuth2AutoConfiguration.java
new file mode 100644
index 0000000000..64c827f6f3
--- /dev/null
+++ b/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/OperatonSpringSecurityOAuth2AutoConfiguration.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
+ * under one or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. Camunda licenses this file to you under the Apache License,
+ * Version 2.0; 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.operaton.bpm.spring.boot.starter.security.oauth2;
+
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.Filter;
+import org.operaton.bpm.engine.rest.security.auth.ProcessEngineAuthenticationFilter;
+import org.operaton.bpm.engine.spring.SpringProcessEngineServicesConfiguration;
+import org.operaton.bpm.spring.boot.starter.OperatonBpmAutoConfiguration;
+import org.operaton.bpm.spring.boot.starter.property.OperatonBpmProperties;
+import org.operaton.bpm.spring.boot.starter.property.WebappProperty;
+import org.operaton.bpm.spring.boot.starter.security.oauth2.impl.OAuth2AuthenticationProvider;
+import org.operaton.bpm.webapp.impl.security.auth.ContainerBasedAuthenticationFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureOrder;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
+import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.core.Ordered;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+import java.util.Map;
+
+@AutoConfigureOrder(OperatonSpringSecurityOAuth2AutoConfiguration.CAMUNDA_OAUTH2_ORDER)
+@AutoConfigureAfter({ OperatonBpmAutoConfiguration.class, SpringProcessEngineServicesConfiguration.class })
+@ConditionalOnBean(OperatonBpmProperties.class)
+@Conditional(ClientsConfiguredCondition.class)
+public class OperatonSpringSecurityOAuth2AutoConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(OperatonSpringSecurityOAuth2AutoConfiguration.class);
+ public static final int CAMUNDA_OAUTH2_ORDER = Ordered.HIGHEST_PRECEDENCE + 100;
+ private final String webappPath;
+
+ public OperatonSpringSecurityOAuth2AutoConfiguration(OperatonBpmProperties properties) {
+ WebappProperty webapp = properties.getWebapp();
+ this.webappPath = webapp.getApplicationPath();
+ }
+
+ @Bean
+ public FilterRegistrationBean> webappAuthenticationFilter() {
+ FilterRegistrationBean filterRegistration = new FilterRegistrationBean<>();
+ filterRegistration.setName("Container Based Authentication Filter");
+ filterRegistration.setFilter(new ContainerBasedAuthenticationFilter());
+ filterRegistration.setInitParameters(Map.of(
+ ProcessEngineAuthenticationFilter.AUTHENTICATION_PROVIDER_PARAM, OAuth2AuthenticationProvider.class.getName()));
+ // make sure the filter is registered after the Spring Security Filter Chain
+ filterRegistration.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER + 1);
+ filterRegistration.addUrlPatterns(webappPath + "/app/*", webappPath + "/api/*");
+ filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
+ return filterRegistration;
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ logger.info("Enabling Operaton Spring Security oauth2 integration");
+
+ http.authorizeHttpRequests(c -> c
+ .requestMatchers(webappPath + "/app/**").authenticated()
+ .requestMatchers(webappPath + "/api/**").authenticated()
+ .anyRequest().permitAll()
+ )
+ .oauth2Login(Customizer.withDefaults())
+ .oidcLogout(Customizer.withDefaults())
+ .oauth2Client(Customizer.withDefaults())
+ .csrf(AbstractHttpConfigurer::disable);
+
+ return http.build();
+ }
+
+}
diff --git a/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/impl/ClientsNotConfiguredCondition.java b/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/impl/ClientsNotConfiguredCondition.java
new file mode 100644
index 0000000000..1513dc73a5
--- /dev/null
+++ b/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/impl/ClientsNotConfiguredCondition.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
+ * under one or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. Camunda licenses this file to you under the Apache License,
+ * Version 2.0; 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.operaton.bpm.spring.boot.starter.security.oauth2.impl;
+
+import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
+import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+/**
+ * Condition that matches if no {@code spring.security.oauth2.client.registration} properties are defined
+ * by inverting the outcome of {@link ClientsConfiguredCondition}.
+ */
+public class ClientsNotConfiguredCondition extends ClientsConfiguredCondition {
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ var matchOutcome = super.getMatchOutcome(context, metadata);
+ return new ConditionOutcome(!matchOutcome.isMatch(), matchOutcome.getConditionMessage());
+ }
+}
diff --git a/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/impl/OAuth2AuthenticationProvider.java b/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/impl/OAuth2AuthenticationProvider.java
new file mode 100644
index 0000000000..30c73a7816
--- /dev/null
+++ b/spring-boot-starter/starter-security/src/main/java/org/operaton/bpm/spring/boot/starter/security/oauth2/impl/OAuth2AuthenticationProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
+ * under one or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. Camunda licenses this file to you under the Apache License,
+ * Version 2.0; 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.operaton.bpm.spring.boot.starter.security.oauth2.impl;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.operaton.bpm.engine.ProcessEngine;
+import org.operaton.bpm.engine.rest.security.auth.AuthenticationResult;
+import org.operaton.bpm.engine.rest.security.auth.impl.ContainerBasedAuthenticationProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+
+public class OAuth2AuthenticationProvider extends ContainerBasedAuthenticationProvider {
+
+ private static final Logger logger = LoggerFactory.getLogger(OAuth2AuthenticationProvider.class);
+
+ @Override
+ public AuthenticationResult extractAuthenticatedUser(HttpServletRequest request, ProcessEngine engine) {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+ if (authentication == null) {
+ logger.debug("Authentication is null");
+ return AuthenticationResult.unsuccessful();
+ }
+
+ if (!(authentication instanceof OAuth2AuthenticationToken)) {
+ logger.debug("Authentication is not OAuth2, it is {}", authentication.getClass());
+ return AuthenticationResult.unsuccessful();
+ }
+ var oauth2 = (OAuth2AuthenticationToken) authentication;
+ String operatonUserId = oauth2.getName();
+ if (operatonUserId == null || operatonUserId.isEmpty()) {
+ logger.debug("UserId is empty");
+ return AuthenticationResult.unsuccessful();
+ }
+
+ logger.debug("Authenticated user '{}'", operatonUserId);
+ return AuthenticationResult.successful(operatonUserId);
+ }
+}
diff --git a/spring-boot-starter/starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starter/starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000..12f67f2201
--- /dev/null
+++ b/spring-boot-starter/starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+org.operaton.bpm.spring.boot.starter.security.oauth2.OperatonBpmSpringSecurityDisableAutoConfiguration
+org.operaton.bpm.spring.boot.starter.security.oauth2.OperatonSpringSecurityOAuth2AutoConfiguration
\ No newline at end of file
diff --git a/spring-boot-starter/starter-security/src/test/java/my/own/custom/spring/boot/project/SampleApplication.java b/spring-boot-starter/starter-security/src/test/java/my/own/custom/spring/boot/project/SampleApplication.java
index 6e670d2314..cf8b4ebbbb 100644
--- a/spring-boot-starter/starter-security/src/test/java/my/own/custom/spring/boot/project/SampleApplication.java
+++ b/spring-boot-starter/starter-security/src/test/java/my/own/custom/spring/boot/project/SampleApplication.java
@@ -18,15 +18,13 @@
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.springframework.boot.SpringApplication;
-import org.springframework.boot.SpringBootConfiguration;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
-@SpringBootConfiguration
-@EnableAutoConfiguration
+@SpringBootApplication
public class SampleApplication {
public static void main(String... args) {
diff --git a/spring-boot-starter/starter-security/src/test/java/my/own/custom/spring/boot/project/SamplePermitAllApplication.java b/spring-boot-starter/starter-security/src/test/java/my/own/custom/spring/boot/project/SamplePermitAllApplication.java
deleted file mode 100644
index b18d8c194d..0000000000
--- a/spring-boot-starter/starter-security/src/test/java/my/own/custom/spring/boot/project/SamplePermitAllApplication.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
- * under one or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information regarding copyright
- * ownership. Camunda licenses this file to you under the Apache License,
- * Version 2.0; 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 my.own.custom.spring.boot.project;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.SpringBootConfiguration;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.context.annotation.Bean;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
-import org.springframework.security.web.SecurityFilterChain;
-
-@SpringBootConfiguration
-@EnableAutoConfiguration
-public class SamplePermitAllApplication {
-
- public static void main(String... args) {
- SpringApplication.run(SamplePermitAllApplication.class, args);
- }
-
- @Bean
- public SecurityFilterChain filterChainPermitAll(HttpSecurity http) throws Exception {
- http.authorizeHttpRequests(customizer -> customizer.anyRequest().permitAll())
- .cors(AbstractHttpConfigurer::disable)
- .csrf(AbstractHttpConfigurer::disable);
- return http.build();
- }
-
-}
diff --git a/spring-boot-starter/starter-security/src/test/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSampleApplicationTest.java b/spring-boot-starter/starter-security/src/test/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSampleApplicationTest.java
index 09fa9e8df4..b3c94e4d9c 100644
--- a/spring-boot-starter/starter-security/src/test/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSampleApplicationTest.java
+++ b/spring-boot-starter/starter-security/src/test/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSampleApplicationTest.java
@@ -24,19 +24,14 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
-import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
-import java.util.List;
-
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
-@SpringBootTest(classes = SampleApplication.class, webEnvironment = RANDOM_PORT)
+@SpringBootTest(classes = SampleApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OperatonBpmSampleApplicationTest {
private String baseUrl;
@@ -52,11 +47,16 @@ public void postConstruct() {
baseUrl = "http://localhost:" + port;
}
+ @Test
+ public void webappApiIsAvailableAndAuthorized() {
+ ResponseEntity entity = testRestTemplate.getForEntity(baseUrl + "/operaton/api/engine/engine/default/user", String.class);
+ assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
+ }
+
@Test
public void restApiIsAvailable() {
ResponseEntity entity = testRestTemplate.getForEntity(baseUrl + "/engine-rest/engine/", String.class);
- // default Spring Security filter chain config redirects to /login
- assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
- assertThat(entity.getHeaders()).contains(entry(HttpHeaders.LOCATION, List.of(baseUrl + "/login")));
+ assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(entity.getBody()).isEqualTo("[{\"name\":\"default\"}]");
}
}
diff --git a/spring-boot-starter/starter-security/src/test/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSamplePermitAllApplicationTest.java b/spring-boot-starter/starter-security/src/test/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSamplePermitAllApplicationTest.java
deleted file mode 100644
index c0335954f7..0000000000
--- a/spring-boot-starter/starter-security/src/test/java/org/operaton/bpm/spring/boot/starter/security/OperatonBpmSamplePermitAllApplicationTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
- * under one or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information regarding copyright
- * ownership. Camunda licenses this file to you under the Apache License,
- * Version 2.0; 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.operaton.bpm.spring.boot.starter.security;
-
-import my.own.custom.spring.boot.project.SamplePermitAllApplication;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.web.client.TestRestTemplate;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest(
- classes = SamplePermitAllApplication.class,
- webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
-)
-public class OperatonBpmSamplePermitAllApplicationTest {
-
- @Autowired
- private TestRestTemplate testRestTemplate;
-
- @Test
- public void restApiIsAvailable() {
- ResponseEntity entity = testRestTemplate.getForEntity("/engine-rest/engine/", String.class);
- assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
- assertThat(entity.getBody()).isEqualTo("[{\"name\":\"default\"}]");
- }
-}