2024-01-03 17:59:04 +00:00
|
|
|
package stirling.software.SPDF.config.security;
|
|
|
|
|
2025-02-24 22:18:34 +00:00
|
|
|
import java.util.Optional;
|
2024-10-20 13:30:58 +02:00
|
|
|
|
2025-01-09 14:40:51 +00:00
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
2024-01-03 17:59:04 +00:00
|
|
|
import org.springframework.beans.factory.annotation.Qualifier;
|
|
|
|
import org.springframework.context.annotation.Bean;
|
|
|
|
import org.springframework.context.annotation.Configuration;
|
2024-11-29 15:11:59 +00:00
|
|
|
import org.springframework.context.annotation.DependsOn;
|
2024-01-03 17:59:04 +00:00
|
|
|
import org.springframework.context.annotation.Lazy;
|
2024-11-29 15:11:59 +00:00
|
|
|
import org.springframework.security.authentication.ProviderManager;
|
2024-10-20 13:30:58 +02:00
|
|
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
2024-01-03 17:59:04 +00:00
|
|
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
|
|
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
2024-02-18 15:47:19 +00:00
|
|
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
2024-05-02 14:52:50 -06:00
|
|
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
2024-01-03 17:59:04 +00:00
|
|
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
|
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
2024-10-14 22:34:41 +01:00
|
|
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
|
|
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
2024-11-29 15:11:59 +00:00
|
|
|
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
2024-01-03 17:59:04 +00:00
|
|
|
import org.springframework.security.web.SecurityFilterChain;
|
|
|
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
|
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
2024-10-29 17:56:29 +00:00
|
|
|
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
|
|
|
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
2024-01-03 17:59:04 +00:00
|
|
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
|
|
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
2024-11-29 15:11:59 +00:00
|
|
|
|
2024-10-14 22:34:41 +01:00
|
|
|
import lombok.extern.slf4j.Slf4j;
|
2025-02-23 13:36:21 +00:00
|
|
|
|
2024-05-12 19:58:34 +02:00
|
|
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
|
|
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
|
|
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
2024-10-20 13:30:58 +02:00
|
|
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
|
|
|
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
|
|
|
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
|
2024-08-16 12:57:37 +02:00
|
|
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
2024-04-29 15:01:22 -06:00
|
|
|
import stirling.software.SPDF.model.ApplicationProperties;
|
2024-10-20 13:30:58 +02:00
|
|
|
import stirling.software.SPDF.model.User;
|
2024-01-03 17:59:04 +00:00
|
|
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
2024-12-24 09:52:53 +00:00
|
|
|
import stirling.software.SPDF.repository.PersistentLoginRepository;
|
2024-01-03 17:59:04 +00:00
|
|
|
|
|
|
|
@Configuration
|
2024-08-16 12:57:37 +02:00
|
|
|
@EnableWebSecurity
|
2024-01-03 17:59:04 +00:00
|
|
|
@EnableMethodSecurity
|
2024-10-14 22:34:41 +01:00
|
|
|
@Slf4j
|
2024-11-29 15:11:59 +00:00
|
|
|
@DependsOn("runningEE")
|
2024-01-03 17:59:04 +00:00
|
|
|
public class SecurityConfiguration {
|
|
|
|
|
2024-12-24 09:52:53 +00:00
|
|
|
private final CustomUserDetailsService userDetailsService;
|
2025-01-09 14:40:51 +00:00
|
|
|
private final UserService userService;
|
2024-12-24 09:52:53 +00:00
|
|
|
private final boolean loginEnabledValue;
|
|
|
|
private final boolean runningEE;
|
2024-04-29 15:01:22 -06:00
|
|
|
|
2024-12-24 09:52:53 +00:00
|
|
|
private final ApplicationProperties applicationProperties;
|
|
|
|
private final UserAuthenticationFilter userAuthenticationFilter;
|
|
|
|
private final LoginAttemptService loginAttemptService;
|
|
|
|
private final FirstLoginFilter firstLoginFilter;
|
|
|
|
private final SessionPersistentRegistry sessionRegistry;
|
|
|
|
private final PersistentLoginRepository persistentLoginRepository;
|
2025-01-09 14:40:51 +00:00
|
|
|
private final GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper;
|
|
|
|
private final RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations;
|
|
|
|
private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver;
|
2024-01-03 17:59:04 +00:00
|
|
|
|
2024-12-24 09:52:53 +00:00
|
|
|
public SecurityConfiguration(
|
|
|
|
PersistentLoginRepository persistentLoginRepository,
|
|
|
|
CustomUserDetailsService userDetailsService,
|
|
|
|
@Lazy UserService userService,
|
|
|
|
@Qualifier("loginEnabled") boolean loginEnabledValue,
|
|
|
|
@Qualifier("runningEE") boolean runningEE,
|
|
|
|
ApplicationProperties applicationProperties,
|
|
|
|
UserAuthenticationFilter userAuthenticationFilter,
|
|
|
|
LoginAttemptService loginAttemptService,
|
|
|
|
FirstLoginFilter firstLoginFilter,
|
2025-01-09 14:40:51 +00:00
|
|
|
SessionPersistentRegistry sessionRegistry,
|
|
|
|
@Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper,
|
|
|
|
@Autowired(required = false)
|
|
|
|
RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
|
|
|
|
@Autowired(required = false)
|
|
|
|
OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) {
|
2024-12-24 09:52:53 +00:00
|
|
|
this.userDetailsService = userDetailsService;
|
|
|
|
this.userService = userService;
|
|
|
|
this.loginEnabledValue = loginEnabledValue;
|
|
|
|
this.runningEE = runningEE;
|
|
|
|
this.applicationProperties = applicationProperties;
|
|
|
|
this.userAuthenticationFilter = userAuthenticationFilter;
|
|
|
|
this.loginAttemptService = loginAttemptService;
|
|
|
|
this.firstLoginFilter = firstLoginFilter;
|
|
|
|
this.sessionRegistry = sessionRegistry;
|
|
|
|
this.persistentLoginRepository = persistentLoginRepository;
|
2025-01-09 14:40:51 +00:00
|
|
|
this.oAuth2userAuthoritiesMapper = oAuth2userAuthoritiesMapper;
|
|
|
|
this.saml2RelyingPartyRegistrations = saml2RelyingPartyRegistrations;
|
|
|
|
this.saml2AuthenticationRequestResolver = saml2AuthenticationRequestResolver;
|
2024-12-24 09:52:53 +00:00
|
|
|
}
|
2024-01-03 17:59:04 +00:00
|
|
|
|
2024-12-24 09:52:53 +00:00
|
|
|
@Bean
|
|
|
|
public PasswordEncoder passwordEncoder() {
|
|
|
|
return new BCryptPasswordEncoder();
|
|
|
|
}
|
2024-02-18 15:47:19 +00:00
|
|
|
|
2024-01-03 17:59:04 +00:00
|
|
|
@Bean
|
|
|
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
2024-12-10 20:39:24 +00:00
|
|
|
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
2024-11-29 15:11:59 +00:00
|
|
|
http.csrf(csrf -> csrf.disable());
|
|
|
|
}
|
2025-02-24 22:18:34 +00:00
|
|
|
|
2024-01-03 17:59:04 +00:00
|
|
|
if (loginEnabledValue) {
|
2024-10-14 22:34:41 +01:00
|
|
|
http.addFilterBefore(
|
|
|
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
2024-11-29 15:11:59 +00:00
|
|
|
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
2024-10-29 17:56:29 +00:00
|
|
|
CookieCsrfTokenRepository cookieRepo =
|
|
|
|
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
|
|
|
CsrfTokenRequestAttributeHandler requestHandler =
|
|
|
|
new CsrfTokenRequestAttributeHandler();
|
|
|
|
requestHandler.setCsrfRequestAttributeName(null);
|
|
|
|
http.csrf(
|
|
|
|
csrf ->
|
2024-10-30 12:46:44 +00:00
|
|
|
csrf.ignoringRequestMatchers(
|
|
|
|
request -> {
|
2024-12-10 20:39:24 +00:00
|
|
|
String apiKey = request.getHeader("X-API-KEY");
|
2024-10-30 12:46:44 +00:00
|
|
|
// If there's no API key, don't ignore CSRF
|
|
|
|
// (return false)
|
|
|
|
if (apiKey == null || apiKey.trim().isEmpty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Validate API key using existing UserService
|
|
|
|
try {
|
|
|
|
Optional<User> user =
|
|
|
|
userService.getUserByApiKey(apiKey);
|
|
|
|
// If API key is valid, ignore CSRF (return
|
|
|
|
// true)
|
|
|
|
// If API key is invalid, don't ignore CSRF
|
|
|
|
// (return false)
|
|
|
|
return user.isPresent();
|
|
|
|
} catch (Exception e) {
|
|
|
|
// If there's any error validating the API
|
|
|
|
// key, don't ignore CSRF
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.csrfTokenRepository(cookieRepo)
|
2024-10-29 17:56:29 +00:00
|
|
|
.csrfTokenRequestHandler(requestHandler));
|
2024-10-14 22:34:41 +01:00
|
|
|
}
|
2024-01-03 17:59:04 +00:00
|
|
|
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
|
|
|
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
2024-02-18 15:47:19 +00:00
|
|
|
http.sessionManagement(
|
|
|
|
sessionManagement ->
|
|
|
|
sessionManagement
|
2024-12-05 15:56:22 +00:00
|
|
|
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
|
2024-03-09 14:03:46 +00:00
|
|
|
.maximumSessions(10)
|
|
|
|
.maxSessionsPreventsLogin(false)
|
2024-08-16 12:57:37 +02:00
|
|
|
.sessionRegistry(sessionRegistry)
|
2024-02-18 15:47:19 +00:00
|
|
|
.expiredUrl("/login?logout=true"));
|
2024-10-20 13:30:58 +02:00
|
|
|
http.authenticationProvider(daoAuthenticationProvider());
|
|
|
|
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
|
|
|
|
http.logout(
|
|
|
|
logout ->
|
|
|
|
logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
|
|
|
.logoutSuccessHandler(
|
|
|
|
new CustomLogoutSuccessHandler(applicationProperties))
|
2024-10-29 17:56:29 +00:00
|
|
|
.clearAuthentication(true)
|
2025-02-24 22:18:34 +00:00
|
|
|
.invalidateHttpSession(true)
|
2024-10-20 13:30:58 +02:00
|
|
|
.deleteCookies("JSESSIONID", "remember-me"));
|
|
|
|
http.rememberMe(
|
2024-12-24 09:52:53 +00:00
|
|
|
rememberMeConfigurer -> // Use the configurator directly
|
|
|
|
rememberMeConfigurer
|
2024-10-20 13:30:58 +02:00
|
|
|
.tokenRepository(persistentTokenRepository())
|
2024-12-24 09:52:53 +00:00
|
|
|
.tokenValiditySeconds( // 14 days
|
|
|
|
14 * 24 * 60 * 60)
|
|
|
|
.userDetailsService( // Your existing UserDetailsService
|
|
|
|
userDetailsService)
|
|
|
|
.useSecureCookie( // Enable secure cookie
|
|
|
|
true)
|
|
|
|
.rememberMeParameter( // Form parameter name
|
|
|
|
"remember-me")
|
|
|
|
.rememberMeCookieName( // Cookie name
|
|
|
|
"remember-me")
|
2024-11-05 14:31:31 +00:00
|
|
|
.alwaysRemember(false));
|
2024-10-20 13:30:58 +02:00
|
|
|
http.authorizeHttpRequests(
|
|
|
|
authz ->
|
|
|
|
authz.requestMatchers(
|
|
|
|
req -> {
|
|
|
|
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("/saml2")
|
|
|
|
|| trimmedUri.endsWith(".svg")
|
|
|
|
|| trimmedUri.startsWith("/register")
|
|
|
|
|| trimmedUri.startsWith("/error")
|
|
|
|
|| trimmedUri.startsWith("/images/")
|
|
|
|
|| trimmedUri.startsWith("/public/")
|
|
|
|
|| trimmedUri.startsWith("/css/")
|
|
|
|
|| trimmedUri.startsWith("/fonts/")
|
|
|
|
|| trimmedUri.startsWith("/js/")
|
|
|
|
|| trimmedUri.startsWith(
|
|
|
|
"/api/v1/info/status");
|
|
|
|
})
|
|
|
|
.permitAll()
|
|
|
|
.anyRequest()
|
|
|
|
.authenticated());
|
|
|
|
// Handle User/Password Logins
|
|
|
|
if (applicationProperties.getSecurity().isUserPass()) {
|
|
|
|
http.formLogin(
|
|
|
|
formLogin ->
|
|
|
|
formLogin
|
|
|
|
.loginPage("/login")
|
|
|
|
.successHandler(
|
|
|
|
new CustomAuthenticationSuccessHandler(
|
|
|
|
loginAttemptService, userService))
|
|
|
|
.failureHandler(
|
|
|
|
new CustomAuthenticationFailureHandler(
|
|
|
|
loginAttemptService, userService))
|
|
|
|
.defaultSuccessUrl("/")
|
|
|
|
.permitAll());
|
|
|
|
}
|
2024-04-29 15:01:22 -06:00
|
|
|
// Handle OAUTH2 Logins
|
2025-02-24 22:18:34 +00:00
|
|
|
if (applicationProperties.getSecurity().isOauth2Active()) {
|
2024-05-03 20:43:48 +01:00
|
|
|
http.oauth2Login(
|
2024-10-20 13:30:58 +02:00
|
|
|
oauth2 ->
|
|
|
|
oauth2.loginPage("/oauth2")
|
2024-12-24 09:52:53 +00:00
|
|
|
.
|
2024-10-20 13:30:58 +02:00
|
|
|
/*
|
|
|
|
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
2025-02-24 22:18:34 +00:00
|
|
|
If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser'
|
2024-10-20 13:30:58 +02:00
|
|
|
is set as true, else login fails with an error message advising the same.
|
|
|
|
*/
|
2024-12-24 09:52:53 +00:00
|
|
|
successHandler(
|
2024-10-20 13:30:58 +02:00
|
|
|
new CustomOAuth2AuthenticationSuccessHandler(
|
|
|
|
loginAttemptService,
|
|
|
|
applicationProperties,
|
|
|
|
userService))
|
|
|
|
.failureHandler(
|
|
|
|
new CustomOAuth2AuthenticationFailureHandler())
|
2024-12-24 09:52:53 +00:00
|
|
|
. // Add existing Authorities from the database
|
|
|
|
userInfoEndpoint(
|
2024-10-20 13:30:58 +02:00
|
|
|
userInfoEndpoint ->
|
|
|
|
userInfoEndpoint
|
|
|
|
.oidcUserService(
|
|
|
|
new CustomOAuth2UserService(
|
|
|
|
applicationProperties,
|
|
|
|
userService,
|
|
|
|
loginAttemptService))
|
|
|
|
.userAuthoritiesMapper(
|
2025-01-09 14:40:51 +00:00
|
|
|
oAuth2userAuthoritiesMapper))
|
2024-10-20 13:30:58 +02:00
|
|
|
.permitAll());
|
|
|
|
}
|
|
|
|
// Handle SAML
|
2025-02-24 22:18:34 +00:00
|
|
|
if (applicationProperties.getSecurity().isSaml2Active() && runningEE) {
|
2024-11-29 15:11:59 +00:00
|
|
|
// Configure the authentication provider
|
|
|
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
|
|
|
new OpenSaml4AuthenticationProvider();
|
|
|
|
authenticationProvider.setResponseAuthenticationConverter(
|
|
|
|
new CustomSaml2ResponseAuthenticationConverter(userService));
|
|
|
|
http.authenticationProvider(authenticationProvider)
|
|
|
|
.saml2Login(
|
|
|
|
saml2 -> {
|
|
|
|
try {
|
2024-10-20 13:30:58 +02:00
|
|
|
saml2.loginPage("/saml2")
|
2024-11-29 15:11:59 +00:00
|
|
|
.relyingPartyRegistrationRepository(
|
2025-01-09 14:40:51 +00:00
|
|
|
saml2RelyingPartyRegistrations)
|
2024-11-29 15:11:59 +00:00
|
|
|
.authenticationManager(
|
|
|
|
new ProviderManager(authenticationProvider))
|
2024-05-12 19:58:34 +02:00
|
|
|
.successHandler(
|
2024-10-20 13:30:58 +02:00
|
|
|
new CustomSaml2AuthenticationSuccessHandler(
|
2024-05-18 23:47:05 +02:00
|
|
|
loginAttemptService,
|
|
|
|
applicationProperties,
|
|
|
|
userService))
|
2024-05-12 19:58:34 +02:00
|
|
|
.failureHandler(
|
2024-10-20 13:30:58 +02:00
|
|
|
new CustomSaml2AuthenticationFailureHandler())
|
2024-11-29 15:11:59 +00:00
|
|
|
.authenticationRequestResolver(
|
2025-01-09 14:40:51 +00:00
|
|
|
saml2AuthenticationRequestResolver);
|
2024-11-29 15:11:59 +00:00
|
|
|
} catch (Exception e) {
|
2025-02-24 22:18:34 +00:00
|
|
|
log.error("Error configuring SAML 2 login", e);
|
2024-11-29 15:11:59 +00:00
|
|
|
throw new RuntimeException(e);
|
|
|
|
}
|
|
|
|
});
|
2024-10-14 22:34:41 +01:00
|
|
|
}
|
2024-01-03 17:59:04 +00:00
|
|
|
} else {
|
2025-02-26 10:22:25 +00:00
|
|
|
log.debug("SAML 2 login is not enabled. Using default.");
|
2024-10-14 22:34:41 +01:00
|
|
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
2024-01-03 17:59:04 +00:00
|
|
|
}
|
|
|
|
return http.build();
|
|
|
|
}
|
|
|
|
|
2024-10-20 13:30:58 +02:00
|
|
|
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
|
|
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
|
|
|
provider.setUserDetailsService(userDetailsService);
|
|
|
|
provider.setPasswordEncoder(passwordEncoder());
|
|
|
|
return provider;
|
|
|
|
}
|
|
|
|
|
2024-01-03 17:59:04 +00:00
|
|
|
@Bean
|
|
|
|
public IPRateLimitingFilter rateLimitingFilter() {
|
2024-12-24 09:52:53 +00:00
|
|
|
// Example limit TODO add config level
|
|
|
|
int maxRequestsPerIp = 1000000;
|
2024-01-03 17:59:04 +00:00
|
|
|
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
public PersistentTokenRepository persistentTokenRepository() {
|
2024-12-24 09:52:53 +00:00
|
|
|
return new JPATokenRepositoryImpl(persistentLoginRepository);
|
2024-01-03 17:59:04 +00:00
|
|
|
}
|
2024-04-21 22:16:39 +02:00
|
|
|
|
|
|
|
@Bean
|
2025-02-24 22:18:34 +00:00
|
|
|
public boolean activeSecurity() {
|
2024-04-21 22:16:39 +02:00
|
|
|
return true;
|
|
|
|
}
|
2024-01-03 17:59:04 +00:00
|
|
|
}
|