fixing oauth redirect issues

# Conflicts:
#	app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java

# Conflicts:
#	app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java
This commit is contained in:
Dario Ghunney Ware
2025-10-31 13:10:00 +00:00
committed by DarioGii
parent b4f9ed031e
commit e6c49d1737
6 changed files with 118 additions and 137 deletions

View File

@@ -71,6 +71,7 @@ public class SecurityConfiguration {
private final boolean loginEnabledValue;
private final boolean runningProOrHigher;
private final ApplicationProperties applicationProperties;
private final ApplicationProperties.Security securityProperties;
private final AppConfig appConfig;
private final UserAuthenticationFilter userAuthenticationFilter;
@@ -90,6 +91,7 @@ public class SecurityConfiguration {
@Qualifier("loginEnabled") boolean loginEnabledValue,
@Qualifier("runningProOrHigher") boolean runningProOrHigher,
AppConfig appConfig,
ApplicationProperties applicationProperties,
ApplicationProperties.Security securityProperties,
UserAuthenticationFilter userAuthenticationFilter,
JwtServiceInterface jwtService,
@@ -106,6 +108,7 @@ public class SecurityConfiguration {
this.loginEnabledValue = loginEnabledValue;
this.runningProOrHigher = runningProOrHigher;
this.appConfig = appConfig;
this.applicationProperties = applicationProperties;
this.securityProperties = securityProperties;
this.userAuthenticationFilter = userAuthenticationFilter;
this.jwtService = jwtService;
@@ -127,53 +130,46 @@ public class SecurityConfiguration {
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration cfg = new CorsConfiguration();
// Set allowed origin patterns
if (appConfig.v2Enabled()) {
// Development mode - allow common development ports
cfg.setAllowedOriginPatterns(
// Read CORS allowed origins from settings
if (applicationProperties.getSystem() != null
&& applicationProperties.getSystem().getCorsAllowedOrigins() != null
&& !applicationProperties.getSystem().getCorsAllowedOrigins().isEmpty()) {
List<String> allowedOrigins = applicationProperties.getSystem().getCorsAllowedOrigins();
cfg.setAllowedOrigins(allowedOrigins);
log.info("CORS configured with allowed origins from settings.yml: {}", allowedOrigins);
// Set allowed methods explicitly
cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
// Set allowed headers explicitly
cfg.setAllowedHeaders(
List.of(
"http://localhost:3000", // Common React dev server
"http://localhost:5173", // Vite default port
"http://localhost:5174", // Vite alternate port
"http://localhost:8080", // Backend port
"http://localhost:*", // Any localhost port
"https://localhost:*" // HTTPS localhost
));
log.info("CORS configured for development mode (v2 enabled)");
"Authorization",
"Content-Type",
"X-Requested-With",
"Accept",
"Origin",
"X-API-KEY",
"X-CSRF-TOKEN"));
// Set exposed headers (headers that the browser can access)
cfg.setExposedHeaders(
List.of("WWW-Authenticate", "X-Total-Count", "X-Page-Number", "X-Page-Size"));
// Allow credentials (cookies, authorization headers)
cfg.setAllowCredentials(true);
// Set max age for preflight cache
cfg.setMaxAge(3600L);
} else {
// Production mode - be more restrictive
// You should configure production domains here
cfg.setAllowedOriginPatterns(
List.of(
"http://localhost:*", // Still allow localhost for local deployments
"https://localhost:*"));
log.info("CORS configured for production mode");
// No CORS origins configured - CORS is disabled (secure by default)
// In production, frontend and backend are served from same origin (no CORS needed)
log.info(
"CORS is disabled - no allowed origins configured in settings.yml (system.corsAllowedOrigins)");
}
// Set allowed methods explicitly
cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
// Set allowed headers explicitly
cfg.setAllowedHeaders(
List.of(
"Authorization",
"Content-Type",
"X-Requested-With",
"Accept",
"Origin",
"X-API-KEY",
"X-CSRF-TOKEN"));
// Set exposed headers (headers that the browser can access)
cfg.setExposedHeaders(
List.of("WWW-Authenticate", "X-Total-Count", "X-Page-Number", "X-Page-Size"));
// Allow credentials (cookies, authorization headers)
cfg.setAllowCredentials(true);
// Set max age for preflight cache
cfg.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", cfg);
return source;
@@ -181,7 +177,7 @@ public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// Enable CORS with our custom configuration
// Enable CORS with custom configuration
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
if (securityProperties.getCsrfDisabled() || !loginEnabledValue) {
@@ -194,11 +190,8 @@ public class SecurityConfiguration {
http.addFilterBefore(
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(
rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
if (v2Enabled) {
http.addFilterBefore(jwtAuthenticationFilter(), UserAuthenticationFilter.class);
}
rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthenticationFilter(), UserAuthenticationFilter.class);
if (!securityProperties.getCsrfDisabled()) {
CookieCsrfTokenRepository cookieRepo =
@@ -337,6 +330,8 @@ public class SecurityConfiguration {
"/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")

View File

@@ -135,18 +135,24 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
}
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 =
requestURI.startsWith(contextPath + "/login")
|| requestURI.startsWith(contextPath + "/signup")
|| requestURI.startsWith(contextPath + "/auth/")
|| requestURI.startsWith(contextPath + "/oauth2")
|| requestURI.startsWith(contextPath + "/api/v1/auth/login")
|| requestURI.startsWith(contextPath + "/api/v1/auth/register")
|| requestURI.startsWith(contextPath + "/api/v1/auth/refresh")
|| requestURI.startsWith(
contextPath + "/api/v1/proprietary/ui-data/account")
|| requestURI.startsWith(contextPath + "/api/v1/config");
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;
}

View File

@@ -105,11 +105,17 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
}
}
// If we still don't have any authentication, deny the request
// If we still don't have any authentication, check if it's a public endpoint. If not, deny the request
if (authentication == null || !authentication.isAuthenticated()) {
String method = request.getMethod();
String contextPath = request.getContextPath();
// Allow public auth endpoints to pass through without authentication
if (isPublicAuthEndpoint(requestURI, contextPath)) {
filterChain.doFilter(request, response);
return;
}
if ("GET".equalsIgnoreCase(method) && !requestURI.startsWith(contextPath + "/login")) {
response.sendRedirect(contextPath + "/login"); // redirect to the login page
} else {
@@ -200,6 +206,23 @@ public class UserAuthenticationFilter 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 authentication
return trimmedUri.startsWith("/login")
|| trimmedUri.startsWith("/auth/")
|| trimmedUri.startsWith("/oauth2")
|| trimmedUri.startsWith("/saml2")
|| trimmedUri.startsWith("/api/v1/auth/login")
|| trimmedUri.startsWith("/api/v1/auth/refresh")
|| trimmedUri.startsWith("/api/v1/auth/logout");
}
private enum UserLoginType {
USERDETAILS("UserDetails"),
OAUTH2USER("OAuth2User"),

View File

@@ -186,6 +186,6 @@ public class CustomOAuth2AuthenticationSuccessHandler
origin.append(":").append(serverPort);
}
return origin.toString() + "/auth/callback#access_token=" + jwt;
return origin + "/auth/callback#access_token=" + jwt;
}
}