FINERACT-1724: Web security adjustments

This commit is contained in:
Arnold Galovics
2023-06-06 11:45:08 +02:00
committed by Arnold Gálovics
parent c437c5a48b
commit d87d483665
4 changed files with 55 additions and 62 deletions
@@ -23,6 +23,7 @@ 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.Collection; import java.util.Collection;
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService; import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
@@ -32,6 +33,7 @@ import org.apache.fineract.infrastructure.core.exceptionmapper.OAuth2ExceptionEn
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken; import org.apache.fineract.infrastructure.security.data.FineractJwtAuthenticationToken;
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog; import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
import org.apache.fineract.infrastructure.security.filter.InsecureTwoFactorAuthenticationFilter;
import org.apache.fineract.infrastructure.security.filter.TenantAwareTenantIdentifierFilter; import org.apache.fineract.infrastructure.security.filter.TenantAwareTenantIdentifierFilter;
import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter;
import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService; import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
@@ -40,6 +42,7 @@ import org.apache.fineract.infrastructure.security.service.TwoFactorService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
@@ -73,7 +76,7 @@ public class OAuth2SecurityConfig {
private ServerProperties serverProperties; private ServerProperties serverProperties;
@Autowired @Autowired
private TwoFactorService twoFactorService; private FineractProperties fineractProperties;
@Autowired @Autowired
private BasicAuthTenantDetailsService basicAuthTenantDetailsService; private BasicAuthTenantDetailsService basicAuthTenantDetailsService;
@@ -89,45 +92,40 @@ public class OAuth2SecurityConfig {
@Autowired @Autowired
private BusinessDateReadPlatformService businessDateReadPlatformService; private BusinessDateReadPlatformService businessDateReadPlatformService;
@Autowired
private ApplicationContext applicationContext;
private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
@Bean
public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
http //
.securityMatcher("/api/**").authorizeHttpRequests((auth) -> {
auth.requestMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/echo").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/authentication").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/self/authentication").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/self/registration").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/self/registration/user").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/twofactor/validate").fullyAuthenticated() //
.requestMatchers("/api/*/twofactor").fullyAuthenticated() //
.requestMatchers("/api/**")
.access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"), selfServiceUserAuthManager())); //
});
if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure());
}
return http.build();
}
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http // http //
.csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients .securityMatcher(antMatcher("/api/**")).authorizeHttpRequests((auth) -> {
auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/api/**")).permitAll() //
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/echo")).permitAll() //
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/authentication")).permitAll() //
.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() //
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() //
.requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() //
.requestMatchers(antMatcher("/api/**"))
.access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"), selfServiceUserAuthManager())); //
}).csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients
.exceptionHandling((ehc) -> ehc.authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) .exceptionHandling((ehc) -> ehc.authenticationEntryPoint(new OAuth2ExceptionEntryPoint()))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter())) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter()))
.authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) // .authenticationEntryPoint(new OAuth2ExceptionEntryPoint())) //
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // .sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
.addFilterAfter(tenantAwareTenantIdentifierFilter(), SecurityContextHolderFilter.class) // .addFilterAfter(tenantAwareTenantIdentifierFilter(), SecurityContextHolderFilter.class);
.addFilterAfter(twoFactorAuthenticationFilter(), BasicAuthenticationFilter.class); //
if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
http.addFilterAfter(twoFactorAuthenticationFilter(), BasicAuthenticationFilter.class);
} else {
http.addFilterAfter(insecureTwoFactorAuthenticationFilter(), BasicAuthenticationFilter.class);
}
if (serverProperties.getSsl().isEnabled()) { if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure()); http.requiresChannel(channel -> channel.requestMatchers(antMatcher("/api/**")).requiresSecure());
} }
return http.build(); return http.build();
@@ -139,9 +137,14 @@ public class OAuth2SecurityConfig {
} }
public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() { public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() {
TwoFactorService twoFactorService = applicationContext.getBean(TwoFactorService.class);
return new TwoFactorAuthenticationFilter(twoFactorService); return new TwoFactorAuthenticationFilter(twoFactorService);
} }
public InsecureTwoFactorAuthenticationFilter insecureTwoFactorAuthenticationFilter() {
return new InsecureTwoFactorAuthenticationFilter();
}
@Bean @Bean
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder(); return PasswordEncoderFactories.createDelegatingPasswordEncoder();
@@ -22,6 +22,7 @@ package org.apache.fineract.infrastructure.core.config;
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 org.apache.fineract.commands.domain.CommandSourceRepository; import org.apache.fineract.commands.domain.CommandSourceRepository;
import org.apache.fineract.commands.service.CommandSourceService; import org.apache.fineract.commands.service.CommandSourceService;
@@ -67,7 +68,6 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.ExceptionTranslationFilter;
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.util.matcher.AntPathRequestMatcher;
@Configuration @Configuration
@ConditionalOnProperty("fineract.security.basicauth.enabled") @ConditionalOnProperty("fineract.security.basicauth.enabled")
@@ -114,48 +114,41 @@ public class SecurityConfig {
@Autowired @Autowired
private IdempotencyStoreHelper idempotencyStoreHelper; private IdempotencyStoreHelper idempotencyStoreHelper;
@Bean
public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
http //
.securityMatcher("/api/**").authorizeHttpRequests((auth) -> {
auth.requestMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/echo").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/authentication").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/self/authentication").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/self/registration").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/self/registration/user").permitAll() //
.requestMatchers(HttpMethod.PUT, "/api/*/instance-mode").permitAll() //
.requestMatchers(HttpMethod.POST, "/api/*/twofactor/validate").fullyAuthenticated() //
.requestMatchers("/api/*/twofactor").fullyAuthenticated() //
.requestMatchers("/api/**").access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"))); //
}) //
.httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint()));
if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers("/api/**").requiresSecure());
}
return http.build();
}
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http // http //
.securityMatcher(antMatcher("/api/**")).authorizeHttpRequests((auth) -> {
auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/api/**")).permitAll() //
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/echo")).permitAll() //
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/authentication")).permitAll() //
.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() //
.requestMatchers(antMatcher(HttpMethod.PUT, "/api/*/instance-mode")).permitAll() //
.requestMatchers(antMatcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() //
.requestMatchers(antMatcher("/api/*/twofactor")).fullyAuthenticated() //
.requestMatchers(antMatcher("/api/**"))
.access(allOf(fullyAuthenticated(), hasAuthority("TWOFACTOR_AUTHENTICATED"))); //
}).httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint())) //
.csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients .csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // .sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
.addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class) .addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class) //
.addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) .addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) //
.addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class) .addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class) //
.addFilterAfter(responseCorsFilter(), CorrelationHeaderFilter.class) // .addFilterAfter(responseCorsFilter(), CorrelationHeaderFilter.class) //
.addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class) // .addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class) //
.addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class) // .addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class) //
.addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); // .addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); //
if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) { if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
http.addFilterAfter(twoFactorAuthenticationFilter(), FineractInstanceModeApiFilter.class); http.addFilterAfter(twoFactorAuthenticationFilter(), ResponseCorsFilter.class);
} else { } else {
http.addFilterAfter(insecureTwoFactorAuthenticationFilter(), FineractInstanceModeApiFilter.class); http.addFilterAfter(insecureTwoFactorAuthenticationFilter(), ResponseCorsFilter.class);
} }
if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.requestMatchers(antMatcher("/api/**")).requiresSecure());
}
return http.build(); return http.build();
} }
@@ -196,7 +189,7 @@ 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(AntPathRequestMatcher.antMatcher("/api/**")); filter.setRequestMatcher(antMatcher("/api/**"));
return filter; return filter;
} }
@@ -106,6 +106,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
// ignore to allow 'preflight' requests from AJAX applications // ignore to allow 'preflight' requests from AJAX applications
// in different origin (domain name) // in different origin (domain name)
filterChain.doFilter(request, response);
} else { } else {
if (requestMatcher.matches(request)) { if (requestMatcher.matches(request)) {
String tenantIdentifier = request.getHeader(this.tenantRequestHeader); String tenantIdentifier = request.getHeader(this.tenantRequestHeader);
@@ -42,9 +42,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog; import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentifierException; import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentifierException;
import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService; import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.filter.GenericFilterBean;
/** /**
@@ -58,8 +56,6 @@ import org.springframework.web.filter.GenericFilterBean;
* *
* Used to support Oauth2 authentication and the service is loaded only when "oauth" profile is active. * Used to support Oauth2 authentication and the service is loaded only when "oauth" profile is active.
*/ */
@Service
@ConditionalOnProperty("fineract.security.oauth.enabled")
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
public class TenantAwareTenantIdentifierFilter extends GenericFilterBean { public class TenantAwareTenantIdentifierFilter extends GenericFilterBean {