From ff577e16f4fa84254d026749c12de8f71f025615 Mon Sep 17 00:00:00 2001 From: Dario Ghunney Ware Date: Fri, 31 Oct 2025 15:02:37 +0000 Subject: [PATCH] cleanup --- .../software/common/util/RequestUriUtils.java | 33 ++++++++ .../configuration/SecurityConfiguration.java | 82 +++++++------------ .../filter/JwtAuthenticationFilter.java | 23 +----- .../filter/UserAuthenticationFilter.java | 2 + ...tomOAuth2AuthenticationSuccessHandler.java | 2 +- 5 files changed, 66 insertions(+), 76 deletions(-) diff --git a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java index 239976b66..6f10f8522 100644 --- a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java @@ -49,4 +49,37 @@ public class RequestUriUtils { || requestURI.startsWith("/fonts") || requestURI.startsWith("/pdfjs")); } + + /** + * Checks if the request URI is a public authentication endpoint that doesn't require + * authentication. This includes login, signup, OAuth callbacks, and public config endpoints. + * + * @param requestURI The full request URI + * @param contextPath The servlet context path + * @return true if the endpoint is public and doesn't require authentication + */ + public static boolean isPublicAuthEndpoint(String requestURI, String contextPath) { + // Remove context path from URI to normalize path matching + String trimmedUri = + requestURI.startsWith(contextPath) + ? requestURI.substring(contextPath.length()) + : requestURI; + + // Public auth endpoints that don't require authentication + return trimmedUri.startsWith("/login") + || trimmedUri.startsWith("/signup") + || trimmedUri.startsWith("/register") + || trimmedUri.startsWith("/auth/") + || trimmedUri.startsWith("/oauth2") + || trimmedUri.startsWith("/saml2") + || trimmedUri.startsWith("/api/v1/auth/login") + || trimmedUri.startsWith("/api/v1/auth/register") + || trimmedUri.startsWith("/api/v1/auth/refresh") + || trimmedUri.startsWith("/api/v1/auth/logout") + || trimmedUri.startsWith("/api/v1/user/register") + || trimmedUri.startsWith("/api/v1/proprietary/ui-data/account") + || trimmedUri.startsWith("/api/v1/config") + || trimmedUri.startsWith("/v1/api-docs") + || trimmedUri.contains("/v1/api-docs"); + } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index 084bb7c58..8dcd4a270 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -37,6 +37,7 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.common.configuration.AppConfig; import stirling.software.common.model.ApplicationProperties; +import stirling.software.common.util.RequestUriUtils; import stirling.software.proprietary.security.CustomAuthenticationFailureHandler; import stirling.software.proprietary.security.CustomAuthenticationSuccessHandler; import stirling.software.proprietary.security.CustomLogoutSuccessHandler; @@ -128,8 +129,6 @@ public class SecurityConfiguration { @Bean public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration cfg = new CorsConfiguration(); - // Read CORS allowed origins from settings if (applicationProperties.getSystem() != null && applicationProperties.getSystem().getCorsAllowedOrigins() != null @@ -137,10 +136,15 @@ public class SecurityConfiguration { List allowedOrigins = applicationProperties.getSystem().getCorsAllowedOrigins(); - cfg.setAllowedOrigins(allowedOrigins); - log.info("CORS configured with allowed origins from settings.yml: {}", allowedOrigins); + CorsConfiguration cfg = new CorsConfiguration(); - // Set allowed methods explicitly + // Use setAllowedOriginPatterns for better wildcard and port support + cfg.setAllowedOriginPatterns(allowedOrigins); + log.debug( + "CORS configured with allowed origin patterns from settings.yml: {}", + allowedOrigins); + + // Set allowed methods explicitly (including OPTIONS for preflight) cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); // Set allowed headers explicitly @@ -163,22 +167,29 @@ public class SecurityConfiguration { // Set max age for preflight cache cfg.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", cfg); + return source; } else { - // No CORS origins configured - CORS is disabled (secure by default) - // In production, frontend and backend are served from same origin (no CORS needed) + // No CORS origins configured - return null to disable CORS processing entirely + // This avoids empty CORS policy that unexpectedly rejects preflights log.info( "CORS is disabled - no allowed origins configured in settings.yml (system.corsAllowedOrigins)"); + return null; } - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", cfg); - return source; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // Enable CORS with custom configuration - http.cors(cors -> cors.configurationSource(corsConfigurationSource())); + // Enable CORS only if we have configured origins + CorsConfigurationSource corsSource = corsConfigurationSource(); + if (corsSource != null) { + http.cors(cors -> cors.configurationSource(corsSource)); + } else { + // Explicitly disable CORS when no origins are configured + http.cors(cors -> cors.disable()); + } if (securityProperties.getCsrfDisabled() || !loginEnabledValue) { http.csrf(CsrfConfigurer::disable); @@ -296,46 +307,11 @@ public class SecurityConfiguration { String uri = req.getRequestURI(); String contextPath = req.getContextPath(); - // Remove the context path from the URI - String trimmedUri = - uri.startsWith(contextPath) - ? uri.substring( - contextPath.length()) - : uri; - return trimmedUri.startsWith("/login") - || trimmedUri.startsWith("/oauth") - || trimmedUri.startsWith("/oauth2") - || trimmedUri.startsWith("/saml2") - || trimmedUri.endsWith(".svg") - || trimmedUri.startsWith("/register") - || trimmedUri.startsWith("/signup") - || trimmedUri.startsWith("/auth/callback") - || trimmedUri.startsWith("/error") - || trimmedUri.startsWith("/images/") - || trimmedUri.startsWith("/public/") - || trimmedUri.startsWith("/css/") - || trimmedUri.startsWith("/fonts/") - || trimmedUri.startsWith("/js/") - || trimmedUri.startsWith("/pdfjs/") - || trimmedUri.startsWith("/pdfjs-legacy/") - || trimmedUri.startsWith("/favicon") - || trimmedUri.startsWith( - "/api/v1/info/status") - || trimmedUri.startsWith("/api/v1/config") - || trimmedUri.startsWith( - "/api/v1/auth/register") - || trimmedUri.startsWith( - "/api/v1/user/register") - || trimmedUri.startsWith( - "/api/v1/auth/login") - || trimmedUri.startsWith( - "/api/v1/auth/refresh") - || trimmedUri.startsWith( - "/api/v1/auth/logout") - || trimmedUri.startsWith( - "/api/v1/proprietary/ui-data/account") - || trimmedUri.startsWith("/v1/api-docs") - || uri.contains("/v1/api-docs"); + // Check if it's a public auth endpoint or static + // resource + return RequestUriUtils.isStaticResource( + contextPath, uri) || RequestUriUtils.isPublicAuthEndpoint( + uri, contextPath); }) .permitAll() .anyRequest() diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java index b4523f060..ace7d3318 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java @@ -1,5 +1,6 @@ package stirling.software.proprietary.security.filter; +import static stirling.software.common.util.RequestUriUtils.isPublicAuthEndpoint; import static stirling.software.common.util.RequestUriUtils.isStaticResource; import static stirling.software.proprietary.security.model.AuthenticationType.OAUTH2; import static stirling.software.proprietary.security.model.AuthenticationType.SAML2; @@ -134,28 +135,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); } - private static boolean isPublicAuthEndpoint(String requestURI, String contextPath) { - // Remove context path from URI to normalize path matching - String trimmedUri = - requestURI.startsWith(contextPath) - ? requestURI.substring(contextPath.length()) - : requestURI; - - // Public auth endpoints that don't require JWT - boolean isPublicAuthEndpoint = - trimmedUri.startsWith("/login") - || trimmedUri.startsWith("/signup") - || trimmedUri.startsWith("/auth/") - || trimmedUri.startsWith("/oauth2") - || trimmedUri.startsWith("/api/v1/auth/login") - || trimmedUri.startsWith("/api/v1/auth/register") - || trimmedUri.startsWith("/api/v1/auth/refresh") - || trimmedUri.startsWith("/api/v1/auth/logout") - || trimmedUri.startsWith("/api/v1/proprietary/ui-data/account") - || trimmedUri.startsWith("/api/v1/config"); - return isPublicAuthEndpoint; - } - private boolean apiKeyExists(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java index 234a029a3..b74295cf7 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java @@ -1,5 +1,7 @@ package stirling.software.proprietary.security.filter; +import static stirling.software.common.util.RequestUriUtils.isPublicAuthEndpoint; + import java.io.IOException; import java.util.List; import java.util.Optional; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java index 1137602b6..2afc43443 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java @@ -186,6 +186,6 @@ public class CustomOAuth2AuthenticationSuccessHandler origin.append(":").append(serverPort); } - return origin + "/auth/callback#access_token=" + jwt; + return origin.toString() + "/auth/callback#access_token=" + jwt; } }