FINERACT-2326: Add configuration options for CORS handling

This commit is contained in:
Adam Saghy
2025-12-08 10:23:17 +01:00
parent df4f43091b
commit 8391614330
5 changed files with 73 additions and 9 deletions
@@ -519,6 +519,7 @@ public class FineractProperties {
private FineractSecurityTwoFactorAuth twoFactor;
private FineractSecurityHsts hsts;
private FineractSecurityOAuth2Properties oauth2;
private CorsProperties cors;
public void set2fa(FineractSecurityTwoFactorAuth twoFactor) {
this.twoFactor = twoFactor;
@@ -688,4 +689,16 @@ public class FineractProperties {
}
}
}
@Getter
@Setter
public static class CorsProperties {
private boolean enabled;
private List<String> allowedOrigins;
private List<String> allowedMethods;
private List<String> allowedHeaders;
private List<String> exposedHeaders;
private boolean allowCredentials;
}
}
@@ -160,8 +160,8 @@ public class SecurityConfig {
.requestMatchers(antMatcher("/api/**"))
.access(allOf(authorizationManagers.toArray(new AuthorizationManager[0]))); //
}).httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint())) //
.cors(Customizer.withDefaults()).csrf(AbstractHttpConfigurer::disable) // NOSONAR only creating a
// service that
.csrf(AbstractHttpConfigurer::disable) // NOSONAR only creating a
// service that
// is used by non-browser clients
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
.addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class) //
@@ -189,6 +189,9 @@ public class SecurityConfig {
http.requiresChannel(channel -> channel.anyRequest().requiresSecure()).headers(
headers -> headers.httpStrictTransportSecurity(hsts -> hsts.includeSubDomains(true).maxAgeInSeconds(31536000)));
}
if (fineractProperties.getSecurity().getCors().isEnabled()) {
http.cors(Customizer.withDefaults());
}
return http.build();
}
@@ -258,13 +261,16 @@ public class SecurityConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(List.of("*"));
CorsConfiguration config = new CorsConfiguration();
FineractProperties.CorsProperties corsConfiguration = fineractProperties.getSecurity().getCors();
config.setAllowedOrigins(corsConfiguration.getAllowedOrigins());
config.setAllowedMethods(corsConfiguration.getAllowedMethods());
config.setAllowedHeaders(corsConfiguration.getAllowedHeaders());
config.setExposedHeaders(corsConfiguration.getExposedHeaders());
config.setAllowCredentials(corsConfiguration.isAllowCredentials()); // if you use cookies / Authorization header
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
source.registerCorsConfiguration("/**", config);
return source;
}
}
@@ -55,6 +55,7 @@ import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -78,6 +79,9 @@ import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.OncePerRequestFilter;
@Configuration
@@ -121,6 +125,10 @@ public class AuthorizationServerConfig {
http.securityMatcher("/swagger-ui/**", "/fineract.json", "/actuator/**", "/legacy-docs/apiLive.htm")
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()).csrf(AbstractHttpConfigurer::disable);
if (fineractProperties.getSecurity().getCors().isEnabled()) {
http.cors(Customizer.withDefaults());
}
return http.build();
}
@@ -137,6 +145,10 @@ public class AuthorizationServerConfig {
.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")))
.apply(authorizationServerConfigurer);
if (fineractProperties.getSecurity().getCors().isEnabled()) {
http.cors(Customizer.withDefaults());
}
return http.build();
}
@@ -144,7 +156,6 @@ public class AuthorizationServerConfig {
@Order(3)
public SecurityFilterChain protectedEndpoints(HttpSecurity http) throws Exception {
http
// .securityMatcher(new AntPathRequestMatcher("/api/**"))
// TODO: Make it configurable
.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> {
auth.anyRequest().authenticated();
@@ -171,9 +182,27 @@ public class AuthorizationServerConfig {
if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
http.addFilterAfter(twoFactorAuthenticationFilter(), CorrelationHeaderFilter.class);
}
if (fineractProperties.getSecurity().getCors().isEnabled()) {
http.cors(Customizer.withDefaults());
}
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
FineractProperties.CorsProperties corsConfiguration = fineractProperties.getSecurity().getCors();
config.setAllowedOrigins(corsConfiguration.getAllowedOrigins());
config.setAllowedMethods(corsConfiguration.getAllowedMethods());
config.setAllowedHeaders(corsConfiguration.getAllowedHeaders());
config.setExposedHeaders(corsConfiguration.getExposedHeaders());
config.setAllowCredentials(corsConfiguration.isAllowCredentials()); // if you use cookies / Authorization header
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Bean
public OncePerRequestFilter tenantAwareAuthenticationFilter() {
return new TenantAwareAuthenticationFilter(resolver(), tenantDetailsService);
@@ -26,6 +26,14 @@ fineract.security.oauth2.enabled=${FINERACT_SECURITY_OAUTH_ENABLED:false}
fineract.security.2fa.enabled=${FINERACT_SECURITY_2FA_ENABLED:false}
fineract.security.hsts.enabled=${FINERACT_SECURITY_HSTS_ENABLED:false}
#CORS configuration
fineract.security.cors.enabled=${FINERACT_SECURITY_CORS_ENABLED:true}
fineract.security.cors.allowed-origins=${FINERACT_SECURITY_CORS_ALLOWED_ORIGINS:"*"}
fineract.security.cors.allowed-methods=${FINERACT_SECURITY_CORS_ALLOWED_METHODS:"*"}
fineract.security.cors.allowed-headers=${FINERACT_SECURITY_CORS_ALLOWED_HEADERS:"*"}
fineract.security.cors.exposed-headers=${FINERACT_SECURITY_CORS_EXPOSED_HEADERS:"*"}
fineract.security.cors.allow-credentials=${FINERACT_SECURITY_CORS_ALLOW_CREDENTIALS:true}
# EXAMPLE: OAuth2 client configuration (frontend-client)
fineract.security.oauth2.client.registrations.frontend-client.client-id=${FINERACT_SECURITY_OAUTH2_CLIENTS_FRONTEND_ID:frontend-client}
fineract.security.oauth2.client.registrations.frontend-client.scopes=${FINERACT_SECURITY_OAUTH2_CLIENTS_FRONTEND_SCOPES:read,write}
@@ -25,6 +25,14 @@ fineract.security.oauth2.enabled=false
fineract.security.2fa.enabled=false
fineract.security.hsts.enabled=false
#CORS configuration
fineract.security.cors.enabled=true
fineract.security.cors.allowed-origins="*"
fineract.security.cors.allowed-methods="*"
fineract.security.cors.allowed-headers="*"
fineract.security.cors.exposed-headers="*"
fineract.security.cors.allow-credentials=true
fineract.tenant.host=localhost
fineract.tenant.port=3306
fineract.tenant.username=root