mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-06 18:30:57 +00:00
295 lines
16 KiB
Java
295 lines
16 KiB
Java
package stirling.software.SPDF.config.security;
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Qualifier;
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
import org.springframework.context.annotation.Lazy;
|
|
import org.springframework.security.authentication.AuthenticationManager;
|
|
import org.springframework.security.authentication.AuthenticationProvider;
|
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
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;
|
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
|
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
|
import org.springframework.security.web.SecurityFilterChain;
|
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
|
import stirling.software.SPDF.config.security.saml.ConvertResponseToAuthentication;
|
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler;
|
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler;
|
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
|
import stirling.software.SPDF.model.ApplicationProperties;
|
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
|
|
|
@Configuration
|
|
@EnableWebSecurity
|
|
@EnableMethodSecurity
|
|
@Slf4j
|
|
public class SecurityConfiguration {
|
|
|
|
@Autowired private CustomUserDetailsService userDetailsService;
|
|
|
|
@Autowired(required = false)
|
|
private GrantedAuthoritiesMapper userAuthoritiesMapper;
|
|
|
|
@Autowired(required = false)
|
|
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
|
|
|
@Bean
|
|
public PasswordEncoder passwordEncoder() {
|
|
return new BCryptPasswordEncoder();
|
|
}
|
|
|
|
@Autowired @Lazy private UserService userService;
|
|
|
|
@Autowired
|
|
@Qualifier("loginEnabled")
|
|
public boolean loginEnabledValue;
|
|
|
|
@Autowired ApplicationProperties applicationProperties;
|
|
|
|
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
|
|
|
@Autowired private LoginAttemptService loginAttemptService;
|
|
|
|
@Autowired private FirstLoginFilter firstLoginFilter;
|
|
@Autowired private SessionPersistentRegistry sessionRegistry;
|
|
|
|
@Autowired private ConvertResponseToAuthentication convertResponseToAuthentication;
|
|
|
|
@Bean
|
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
http.authenticationManager(authenticationManager(http));
|
|
|
|
if (loginEnabledValue) {
|
|
http.addFilterBefore(
|
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
|
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
|
http.csrf(csrf -> csrf.disable());
|
|
}
|
|
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
|
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
|
http.sessionManagement(
|
|
sessionManagement ->
|
|
sessionManagement
|
|
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
|
|
.maximumSessions(10)
|
|
.maxSessionsPreventsLogin(false)
|
|
.sessionRegistry(sessionRegistry)
|
|
.expiredUrl("/login?logout=true"));
|
|
|
|
http.formLogin(
|
|
formLogin ->
|
|
formLogin
|
|
.loginPage("/login")
|
|
.successHandler(
|
|
new CustomAuthenticationSuccessHandler(
|
|
loginAttemptService, userService))
|
|
.defaultSuccessUrl("/")
|
|
.failureHandler(
|
|
new CustomAuthenticationFailureHandler(
|
|
loginAttemptService, userService))
|
|
.permitAll())
|
|
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()))
|
|
.logout(
|
|
logout ->
|
|
logout.logoutRequestMatcher(
|
|
new AntPathRequestMatcher("/logout"))
|
|
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
|
|
.invalidateHttpSession(true) // Invalidate session
|
|
.deleteCookies("JSESSIONID", "remember-me"))
|
|
.rememberMe(
|
|
rememberMeConfigurer ->
|
|
rememberMeConfigurer // Use the configurator directly
|
|
.key("uniqueAndSecret")
|
|
.tokenRepository(persistentTokenRepository())
|
|
.tokenValiditySeconds(1209600) // 2 weeks
|
|
)
|
|
.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 OAUTH2 Logins
|
|
if (applicationProperties.getSecurity().getOauth2() != null
|
|
&& applicationProperties.getSecurity().getOauth2().getEnabled()
|
|
&& !applicationProperties
|
|
.getSecurity()
|
|
.getLoginMethod()
|
|
.equalsIgnoreCase("normal")) {
|
|
|
|
http.oauth2Login(
|
|
oauth2 ->
|
|
oauth2.loginPage("/oauth2")
|
|
/*
|
|
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
|
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
|
|
is set as true, else login fails with an error message advising the same.
|
|
*/
|
|
.successHandler(
|
|
new CustomOAuth2AuthenticationSuccessHandler(
|
|
loginAttemptService,
|
|
applicationProperties,
|
|
userService))
|
|
.failureHandler(
|
|
new CustomOAuth2AuthenticationFailureHandler())
|
|
// Add existing Authorities from the database
|
|
.userInfoEndpoint(
|
|
userInfoEndpoint ->
|
|
userInfoEndpoint
|
|
.oidcUserService(
|
|
new CustomOAuth2UserService(
|
|
applicationProperties,
|
|
userService,
|
|
loginAttemptService))
|
|
.userAuthoritiesMapper(
|
|
userAuthoritiesMapper)))
|
|
.logout(
|
|
logout ->
|
|
logout.logoutSuccessHandler(
|
|
new CustomOAuth2LogoutSuccessHandler(
|
|
applicationProperties)));
|
|
}
|
|
|
|
// Handle SAML
|
|
if (applicationProperties.getSecurity().getSaml() != null
|
|
&& applicationProperties.getSecurity().getSaml().getEnabled()
|
|
&& !applicationProperties
|
|
.getSecurity()
|
|
.getLoginMethod()
|
|
.equalsIgnoreCase("normal")) {
|
|
http.saml2Login(
|
|
saml2 -> {
|
|
saml2.loginPage("/saml2")
|
|
.relyingPartyRegistrationRepository(
|
|
relyingPartyRegistrationRepository)
|
|
.successHandler(
|
|
new CustomSAMLAuthenticationSuccessHandler(
|
|
loginAttemptService,
|
|
userService,
|
|
applicationProperties))
|
|
.failureHandler(
|
|
new CustomSAMLAuthenticationFailureHandler());
|
|
})
|
|
.addFilterBefore(
|
|
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
|
}
|
|
} else {
|
|
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
|
http.csrf(csrf -> csrf.disable());
|
|
}
|
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
|
}
|
|
|
|
return http.build();
|
|
}
|
|
|
|
@Bean
|
|
@ConditionalOnProperty(
|
|
name = "security.saml.enabled",
|
|
havingValue = "true",
|
|
matchIfMissing = false)
|
|
public AuthenticationProvider samlAuthenticationProvider() {
|
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
|
new OpenSaml4AuthenticationProvider();
|
|
authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication);
|
|
return authenticationProvider;
|
|
}
|
|
|
|
// @Bean
|
|
// public AuthenticationProvider daoAuthenticationProvider() {
|
|
// DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
|
// provider.setUserDetailsService(userDetailsService); // UserDetailsService
|
|
// provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder
|
|
// return provider;
|
|
// }
|
|
|
|
@Bean
|
|
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
|
AuthenticationManagerBuilder authenticationManagerBuilder =
|
|
http.getSharedObject(AuthenticationManagerBuilder.class);
|
|
|
|
// authenticationManagerBuilder =
|
|
// authenticationManagerBuilder.authenticationProvider(
|
|
// daoAuthenticationProvider()); // Benutzername/Passwort
|
|
|
|
if (applicationProperties.getSecurity().getSaml() != null
|
|
&& applicationProperties.getSecurity().getSaml().getEnabled()) {
|
|
authenticationManagerBuilder.authenticationProvider(
|
|
samlAuthenticationProvider()); // SAML
|
|
}
|
|
return authenticationManagerBuilder.build();
|
|
}
|
|
|
|
@Bean
|
|
public IPRateLimitingFilter rateLimitingFilter() {
|
|
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
|
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
|
|
}
|
|
|
|
@Bean
|
|
public PersistentTokenRepository persistentTokenRepository() {
|
|
return new JPATokenRepositoryImpl();
|
|
}
|
|
|
|
@Bean
|
|
public boolean activSecurity() {
|
|
return true;
|
|
}
|
|
|
|
// // Only Dev test
|
|
// @Bean
|
|
// public WebSecurityCustomizer webSecurityCustomizer() {
|
|
// return (web) ->
|
|
// web.ignoring()
|
|
// .requestMatchers(
|
|
// "/css/**", "/images/**", "/js/**", "/**.svg",
|
|
// "/pdfjs-legacy/**");
|
|
// }
|
|
}
|