FINERACT-2421: fix(security): migrate request matchers to PathPatternRequestMatcher (no behavior change)

This commit is contained in:
Aman-Mittal
2026-01-08 22:38:40 +01:00
committed by Adam Saghy
parent d1e456ef43
commit 9cc14e7fdb
@@ -23,11 +23,9 @@ import static org.apache.fineract.infrastructure.security.vote.SelfServiceUserAu
import static org.springframework.security.authorization.AuthenticatedAuthorizationManager.fullyAuthenticated; import static org.springframework.security.authorization.AuthenticatedAuthorizationManager.fullyAuthenticated;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority; import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
import static org.springframework.security.authorization.AuthorizationManagers.allOf; import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService; import org.apache.fineract.infrastructure.cache.service.CacheWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -72,6 +70,7 @@ import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext; import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.context.SecurityContextHolderFilter; import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@@ -81,18 +80,19 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@EnableMethodSecurity @EnableMethodSecurity
public class SecurityConfig { public class SecurityConfig {
private static final PathPatternRequestMatcher.Builder API_MATCHER = PathPatternRequestMatcher.withDefaults();
private static final String ALL_FUNCTIONS = "ALL_FUNCTIONS";
private static final String ALL_FUNCTIONS_READ = "ALL_FUNCTIONS_READ";
private static final String ALL_FUNCTIONS_WRITE = "ALL_FUNCTIONS_WRITE";
@Autowired @Autowired
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
@Autowired @Autowired
private TenantAwareJpaPlatformUserDetailsService userDetailsService; private TenantAwareJpaPlatformUserDetailsService userDetailsService;
@Autowired @Autowired
private FineractProperties fineractProperties; private FineractProperties fineractProperties;
@Autowired @Autowired
private ServerProperties serverProperties; private ServerProperties serverProperties;
@Autowired @Autowired
private ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer; private ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
@Autowired @Autowired
@@ -109,7 +109,6 @@ public class SecurityConfig {
private MDCWrapper mdcWrapper; private MDCWrapper mdcWrapper;
@Autowired @Autowired
private FineractRequestContextHolder fineractRequestContextHolder; private FineractRequestContextHolder fineractRequestContextHolder;
@Autowired(required = false) @Autowired(required = false)
private LoanCOBFilterHelper loanCOBFilterHelper; private LoanCOBFilterHelper loanCOBFilterHelper;
@Autowired @Autowired
@@ -117,62 +116,64 @@ public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http //
.securityMatcher(antMatcher("/api/**")).authorizeHttpRequests((auth) -> { http.securityMatcher(API_MATCHER.matcher("/api/**")).authorizeHttpRequests(auth -> {
List<AuthorizationManager<RequestAuthorizationContext>> authorizationManagers = new ArrayList<>();
authorizationManagers.add(fullyAuthenticated()); List<AuthorizationManager<RequestAuthorizationContext>> authorizationManagers = new ArrayList<>();
if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) { authorizationManagers.add(fullyAuthenticated());
authorizationManagers.add(hasAuthority("TWOFACTOR_AUTHENTICATED"));
} if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
if (fineractProperties.getModule().getSelfService().isEnabled()) { authorizationManagers.add(hasAuthority("TWOFACTOR_AUTHENTICATED"));
auth.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/authentication")).permitAll() // }
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/registration")).permitAll() //
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/self/registration/user")).permitAll(); // if (fineractProperties.getModule().getSelfService().isEnabled()) {
authorizationManagers.add(selfServiceUserAuthManager()); auth.requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/self/authentication")).permitAll()
} .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/self/registration")).permitAll()
auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/api/**")).permitAll() // .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/self/registration/user")).permitAll();
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/echo")).permitAll() // authorizationManagers.add(selfServiceUserAuthManager());
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/authentication")).permitAll() // }
.requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/instance-mode")).permitAll() //
// businessdate auth.requestMatchers(API_MATCHER.matcher(HttpMethod.OPTIONS, "/api/**")).permitAll()
.requestMatchers(antMatcher(HttpMethod.GET, "/api/*/businessdate/*")) .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/echo")).permitAll()
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_BUSINESS_DATE") .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/authentication")).permitAll()
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/businessdate")) .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/instance-mode")).permitAll()
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_BUSINESS_DATE") // businessdate
// external .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/businessdate/*"))
.requestMatchers(antMatcher(HttpMethod.GET, "/api/*/externalevents/configuration")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_BUSINESS_DATE")
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_EXTERNAL_EVENT_CONFIGURATION") .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/businessdate"))
.requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/externalevents/configuration")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_BUSINESS_DATE")
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_EXTERNAL_EVENT_CONFIGURATION") // external
// cache .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/externalevents/configuration"))
.requestMatchers(antMatcher(HttpMethod.GET, "/api/*/caches")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_EXTERNAL_EVENT_CONFIGURATION")
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_CACHE") .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/externalevents/configuration"))
.requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/caches")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_EXTERNAL_EVENT_CONFIGURATION")
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_CACHE") // cache
// currency .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/caches"))
.requestMatchers(antMatcher(HttpMethod.GET, "/api/*/currencies")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_CACHE")
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ", "READ_CURRENCY") .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/caches"))
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/currencies")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_CACHE")
.hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_WRITE", "UPDATE_CURRENCY") // currency
// ... .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/currencies"))
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() // .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_CURRENCY")
.requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() // .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/currencies"))
.requestMatchers(antMatcher("/api/**")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_CURRENCY")
.access(allOf(authorizationManagers.toArray(new AuthorizationManager[0]))); //
}).httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint())) // .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated()
.csrf(AbstractHttpConfigurer::disable) // NOSONAR only creating a .requestMatchers(API_MATCHER.matcher("/api/*/twofactor")).fullyAuthenticated()
// service that .requestMatchers(API_MATCHER.matcher("/api/**"))
// is used by non-browser clients .access(allOf(authorizationManagers.toArray(new AuthorizationManager[0])));
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // }).httpBasic(hb -> hb.authenticationEntryPoint(basicAuthenticationEntryPoint())).csrf(AbstractHttpConfigurer::disable)
.addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class) // .sessionManagement(smc -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) // .addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class)
.addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class) // .addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class)
.addFilterAfter(fineractInstanceModeApiFilter(), CorrelationHeaderFilter.class); // .addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class)
if (!Objects.isNull(loanCOBFilterHelper)) { .addFilterAfter(fineractInstanceModeApiFilter(), CorrelationHeaderFilter.class);
http.addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class) //
.addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); // if (loanCOBFilterHelper != null) {
http.addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class).addFilterAfter(idempotencyStoreFilter(),
LoanCOBApiFilter.class);
} else { } else {
http.addFilterAfter(idempotencyStoreFilter(), FineractInstanceModeApiFilter.class); // http.addFilterAfter(idempotencyStoreFilter(), FineractInstanceModeApiFilter.class);
} }
if (fineractProperties.getIpTracking().isEnabled()) { if (fineractProperties.getIpTracking().isEnabled()) {
http.addFilterAfter(callerIpTrackingFilter(), RequestResponseFilter.class); http.addFilterAfter(callerIpTrackingFilter(), RequestResponseFilter.class);
@@ -182,16 +183,18 @@ public class SecurityConfig {
} }
if (serverProperties.getSsl().isEnabled()) { if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers(antMatcher("/api/**")).requiresSecure()); http.requiresChannel(channel -> channel.requestMatchers(API_MATCHER.matcher("/api/**")).requiresSecure());
} }
if (fineractProperties.getSecurity().getHsts().isEnabled()) { if (fineractProperties.getSecurity().getHsts().isEnabled()) {
http.requiresChannel(channel -> channel.anyRequest().requiresSecure()).headers( http.requiresChannel(channel -> channel.anyRequest().requiresSecure()).headers(
headers -> headers.httpStrictTransportSecurity(hsts -> hsts.includeSubDomains(true).maxAgeInSeconds(31536000))); headers -> headers.httpStrictTransportSecurity(hsts -> hsts.includeSubDomains(true).maxAgeInSeconds(31536000)));
} }
if (fineractProperties.getSecurity().getCors().isEnabled()) { if (fineractProperties.getSecurity().getCors().isEnabled()) {
http.cors(Customizer.withDefaults()); http.cors(Customizer.withDefaults());
} }
return http.build(); return http.build();
} }
@@ -228,7 +231,8 @@ public class SecurityConfig {
TenantAwareBasicAuthenticationFilter filter = new TenantAwareBasicAuthenticationFilter(authenticationManagerBean(), TenantAwareBasicAuthenticationFilter filter = new TenantAwareBasicAuthenticationFilter(authenticationManagerBean(),
basicAuthenticationEntryPoint(), toApiJsonSerializer, configurationDomainService, cacheWritePlatformService, basicAuthenticationEntryPoint(), toApiJsonSerializer, configurationDomainService, cacheWritePlatformService,
userNotificationService, basicAuthTenantDetailsService, businessDateReadPlatformService); userNotificationService, basicAuthTenantDetailsService, businessDateReadPlatformService);
filter.setRequestMatcher(antMatcher("/api/**"));
filter.setRequestMatcher(API_MATCHER.matcher("/api/**"));
return filter; return filter;
} }