package net.gepafin.tendermanagement.config; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; import org.bouncycastle.util.io.pem.PemReader; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.core.AuthnContextClassRef; import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration; import org.opensaml.saml.saml2.core.RequestedAuthnContext; import org.opensaml.saml.saml2.core.impl.AuthnContextClassRefBuilder; import org.opensaml.saml.saml2.core.impl.RequestedAuthnContextBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 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.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import net.gepafin.tendermanagement.config.jwt.JWTFilter; import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.entities.SamlResponseLogEntity; import net.gepafin.tendermanagement.repositories.SamlResponseLogRepository; //import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext; //import org.springframework.security.saml2.core.Saml2AuthenticationRequest; @Configuration @EnableWebSecurity @EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class); private final TokenProvider tokenProvider; @Value("${base-url}") String baseUrl; @Autowired private SamlResponseLogRepository samlResponseLogRepository; @Autowired public SecurityConfig(TokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } // // @Bean // public Saml2AuthenticationRequestResolver authenticationRequestResolver() { // return (Saml2AuthenticationRequestContext context) -> { // Saml2AuthenticationRequest request = Saml2AuthenticationRequest.withAuthenticationRequestContext(context) // .authenticationContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:SecureRemotePassword") // Add context here // .build(); // return request; // }; // } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) { return new MvcRequestMatcher.Builder(introspector); } @Bean public WebSecurityCustomizer webSecurityCustomizer(MvcRequestMatcher.Builder mvc) { return (web) -> web.ignoring().requestMatchers(mvc.pattern(HttpMethod.OPTIONS, "/**")) .requestMatchers(new AntPathRequestMatcher("/i18n/**")) .requestMatchers(new AntPathRequestMatcher("/content/**")) .requestMatchers(new AntPathRequestMatcher("/swagger-ui/index.html")) .requestMatchers(new AntPathRequestMatcher("/swagger-ui/**")); } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setMaxAge(3600l); if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) { source.registerCorsConfiguration("/v1/**", config); source.registerCorsConfiguration("/management/**", config); source.registerCorsConfiguration("/v1/api-docs", config); } return new CorsFilter(source); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> auth // Allow public access to the login endpoints .requestMatchers("/v1/user/login").permitAll() // JWT-based login .requestMatchers("/v1/saml/**").permitAll() // JWT-based login .requestMatchers("/saml2/**").permitAll() // SAML login initiation .requestMatchers("/swagger-ui/**").permitAll() // Swagger docs .requestMatchers("/v1/api-docs/**").permitAll() // API docs .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(corsFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new JWTFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class) // Add SAML2 login configuration (for BENEFICIARI) /* * .saml2Login(saml -> saml.loginPage("/saml/login") // Entry point for SAML * login .defaultSuccessUrl("/") // Redirect after successful SAML login ); */ .saml2Login(saml -> saml.defaultSuccessUrl("/") .successHandler((request, response, authentication) -> { logger.error("SAML success login"); SamlResponseLogEntity samlResponseLogEntity = new SamlResponseLogEntity(); samlResponseLogEntity.setRequest(request.toString()); samlResponseLogEntity.setResponse(response.toString()); samlResponseLogEntity.setAuthenticationObject(authentication.toString()); samlResponseLogRepository.save(samlResponseLogEntity); try { ObjectMapper objectMapper = new ObjectMapper(); // Create a new SAML log entity SamlResponseLogEntity samlResponseLogEntity1 = new SamlResponseLogEntity(); // Convert request, response, and authentication to JSON format String requestJson = objectMapper.writeValueAsString(request.getParameterMap()); // Assuming request params to JSON String responseJson = objectMapper.writeValueAsString(response); // This may need to be adapted based on your response object String authenticationJson = objectMapper.writeValueAsString(authentication); // Authentication object to JSON // Set the JSON strings in the entity samlResponseLogEntity1.setRequest(requestJson); samlResponseLogEntity1.setResponse(responseJson); samlResponseLogEntity1.setAuthenticationObject(authenticationJson); samlResponseLogRepository.save(samlResponseLogEntity1); logger.info("SAML Request: " + requestJson); logger.info("SAML Response: " + responseJson); logger.info("Authentication Details: " + authenticationJson); }catch(Exception e) { logger.info("Exception object" + e); } // samlResponseLogRepository logger.info("SAML login successful for user: " + authentication.getName()); response.sendRedirect("http://gepafin-staging-fe.s3-website.eu-central-1.amazonaws.com/"); }).failureHandler((request, response, exception) -> { logger.error("SAML login failed: " + exception.getMessage()); SamlResponseLogEntity samlResponseLogEntity = new SamlResponseLogEntity(); samlResponseLogEntity.setRequest(request.toString()); samlResponseLogEntity.setResponse(response.toString()); samlResponseLogEntity.setExceptionObject(exception.toString()); samlResponseLogRepository.save(samlResponseLogEntity); try { ObjectMapper objectMapper = new ObjectMapper(); // Create a new SAML log entity SamlResponseLogEntity samlResponseLogEntity1 = new SamlResponseLogEntity(); // Convert request, response, and authentication to JSON format String requestJson = objectMapper.writeValueAsString(request.getParameterMap()); // Assuming request params to JSON String responseJson = objectMapper.writeValueAsString(response); // This may need to be adapted based on your response object String exceptionJson = objectMapper.writeValueAsString(exception); // Authentication object to JSON // Set the JSON strings in the entity samlResponseLogEntity1.setRequest(requestJson); samlResponseLogEntity1.setResponse(responseJson); samlResponseLogEntity1.setAuthenticationObject(exceptionJson); samlResponseLogRepository.save(samlResponseLogEntity1); logger.info("SAML Request: " + requestJson); logger.info("SAML Response: " + responseJson); logger.info("exception Details: " + exceptionJson); }catch(Exception e) { logger.info("Exception object" + e); } response.sendRedirect("http://gepafin-staging-fe.s3-website.eu-central-1.amazonaws.com/login"); })); return http.build(); } @Bean public OpenAPI customOpenAPI() { return new OpenAPI() .addServersItem(new Server().url("/")) .addSecurityItem(new SecurityRequirement().addList("bearer-key")) .components(new Components().addSecuritySchemes("bearer-key", new SecurityScheme().type(SecurityScheme.Type.HTTP) .scheme("bearer").bearerFormat("JWT"))); } @Bean public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { String entityId = baseUrl + "/v1/saml/gw/metadata"; String acsUrl = baseUrl + "/login/saml2/sso/loginumbria"; RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("loginumbria") .entityId(entityId) .signingX509Credentials(credentials -> { try { credentials.add(Saml2X509Credential.signing(readPrivateKey(), readCertificate())); } catch (Exception e) { e.printStackTrace(); } }) .assertionConsumerServiceLocation(acsUrl) .assertingPartyDetails(details -> details.entityId("https://federatest.umbriadigitale.it/gw/metadata") .singleSignOnServiceLocation("https://federatest.umbriadigitale.it/gw/SSOProxy/SAML2") .singleSignOnServiceBinding(Saml2MessageBinding.POST).wantAuthnRequestsSigned(true).build() ) .build(); return new InMemoryRelyingPartyRegistrationRepository(registration); } @Bean public Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) { RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations); OpenSaml4AuthenticationRequestResolver authenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(registrationResolver); // Customize and log the AuthnRequest after setting the context authenticationRequestResolver.setAuthnRequestCustomizer((context) -> { context.getAuthnRequest().setRequestedAuthnContext(buildRequestedAuthnContext()); // Log the SAML AuthnRequest after setting the authentication context String samlRequest = SamlRequestLogger.convertSAMLObjectToString(context.getAuthnRequest()); logger.info("SAML AuthnRequest after setting context: " + samlRequest); }); return authenticationRequestResolver; } private RequestedAuthnContext buildRequestedAuthnContext() { AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder(); AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject( SAMLConstants.SAML20_NS, AuthnContextClassRef.DEFAULT_ELEMENT_LOCAL_NAME, SAMLConstants.SAML20_PREFIX ); // Set the SPID Level 2 authentication context authnContextClassRef.setURI("urn:oasis:names:tc:SAML:2.0:ac:classes:SecureRemotePassword"); RequestedAuthnContextBuilder requestedAuthnContextBuilder = new RequestedAuthnContextBuilder(); RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject(); requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT); requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef); return requestedAuthnContext; } public PrivateKey readPrivateKey() throws Exception { // Path to your private key PEM file try (PemReader pemReader = new PemReader(new InputStreamReader(readKey("dev/saml/private-key.pem")))) { // Read the PEM content byte[] pemContent = pemReader.readPemObject().getContent(); // Decode the PEM content PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pemContent); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // Use RSA algorithm // Generate and return the PrivateKey return keyFactory.generatePrivate(keySpec); } } public X509Certificate readCertificate() throws Exception { // Path to your certificate PEM fileFile try (InputStream inStream = readKey("dev/saml/public-cert.pem")) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); return (X509Certificate) certFactory.generateCertificate(inStream); } } public InputStream readKey(String path) throws IOException { ClassLoader classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream(path); if (inputStream == null) { throw new FileNotFoundException("file not found : "+path); } return inputStream; } }