diff --git a/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/CommonSecurityConfiguration.java b/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/CommonSecurityConfiguration.java index bf0b9b1eb..f5e4b589d 100644 --- a/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/CommonSecurityConfiguration.java +++ b/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/CommonSecurityConfiguration.java @@ -33,6 +33,7 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfigurationSource; /** * Common configurations for security @@ -43,9 +44,16 @@ @ConditionalOnExpression("#{${" + CommonAuthorizationProperties.ENABLED_PROPERTY_KEY + ":false}}") public class CommonSecurityConfiguration { + private final CorsConfigurationSource corsConfigurationSource; + + public CommonSecurityConfiguration(CorsConfigurationSource corsConfigurationSource) { + this.corsConfigurationSource = corsConfigurationSource; + } + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http + .cors(cors -> cors.configurationSource(corsConfigurationSource)) .authorizeHttpRequests(authorize -> authorize .requestMatchers("/actuator/health/**").permitAll() .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() diff --git a/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/BaSyxHTTPConfiguration.java b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/BaSyxHTTPConfiguration.java index 3e6d2ed85..b832d1c9f 100644 --- a/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/BaSyxHTTPConfiguration.java +++ b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/BaSyxHTTPConfiguration.java @@ -35,6 +35,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -103,4 +106,45 @@ private void configureOrigins(String[] allowedOrigins, String[] allowedMethods, } }; } + + /** + * Creates a {@link CorsConfigurationSource} that can be used by Spring Security + * to apply CORS headers even on error responses (e.g., 401, 403). + * This ensures consistent CORS behavior between Spring MVC and Spring Security. + * + * @param configurationUrlProviders + * @param allowedOrigins + * @param allowedMethods + * @return + */ + @Bean + public CorsConfigurationSource corsConfigurationSource(List configurationUrlProviders, + @Value("${basyx.cors.allowed-origins:}") String[] allowedOrigins, + @Value("${basyx.cors.allowed-methods:}") String[] allowedMethods) { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + + if (allowedOrigins.length == 0 && allowedMethods.length == 0) { + return source; + } + + CorsConfiguration configuration = new CorsConfiguration(); + + if (allowedOrigins.length > 0) { + configuration.setAllowedOriginPatterns(Arrays.asList(allowedOrigins)); + } + + if (allowedMethods.length > 0) { + configuration.setAllowedMethods(Arrays.asList(allowedMethods)); + } + + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); + + for (CorsPathPatternProvider provider : configurationUrlProviders) { + logger.info("Registering CORS configuration for path pattern: " + provider.getPathPattern()); + source.registerCorsConfiguration(provider.getPathPattern(), configuration); + } + + return source; + } }