Refactored saml code
This commit is contained in:
@@ -0,0 +1,197 @@
|
|||||||
|
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 java.time.Instant;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
|
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
||||||
|
import org.opensaml.saml.common.SAMLVersion;
|
||||||
|
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.AuthnRequest;
|
||||||
|
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.opensaml.security.x509.BasicX509Credential;
|
||||||
|
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
|
||||||
|
import org.opensaml.xmlsec.signature.Signature;
|
||||||
|
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||||
|
import org.opensaml.xmlsec.signature.support.Signer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SamlConfig {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(SamlConfig.class);
|
||||||
|
|
||||||
|
@Value("${base-url}")
|
||||||
|
String baseUrl;
|
||||||
|
|
||||||
|
@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)
|
||||||
|
.verificationX509Credentials(credentials -> {
|
||||||
|
try {
|
||||||
|
// Load the IDP's public certificate for verifying the SAML response signature
|
||||||
|
credentials.add(Saml2X509Credential.verification(readIdpCertificate()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthnRequest createSignedAuthnRequest(PrivateKey privateKey, X509Certificate certificate) throws Exception {
|
||||||
|
AuthnRequest authnRequest = (AuthnRequest) XMLObjectProviderRegistrySupport.getBuilderFactory()
|
||||||
|
.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME)
|
||||||
|
.buildObject(AuthnRequest.DEFAULT_ELEMENT_NAME);
|
||||||
|
|
||||||
|
authnRequest.setID("_" + UUID.randomUUID().toString());
|
||||||
|
authnRequest.setVersion(SAMLVersion.VERSION_20);
|
||||||
|
// authnRequest.setIssueInstant(new DateTime());
|
||||||
|
authnRequest.setIssueInstant(Instant.now());
|
||||||
|
|
||||||
|
|
||||||
|
// Sign the AuthnRequest
|
||||||
|
// BasicCredential signingCredential = new BasicCredential(certificate, privateKey);
|
||||||
|
BasicX509Credential signingCredential = new BasicX509Credential(certificate, privateKey);
|
||||||
|
|
||||||
|
Signature signature = (Signature) XMLObjectProviderRegistrySupport.getBuilderFactory()
|
||||||
|
.getBuilder(Signature.DEFAULT_ELEMENT_NAME)
|
||||||
|
.buildObject(Signature.DEFAULT_ELEMENT_NAME);
|
||||||
|
|
||||||
|
signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
|
||||||
|
signature.setSigningCredential(signingCredential);
|
||||||
|
signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); // Set RSA-SHA1
|
||||||
|
|
||||||
|
authnRequest.setSignature(signature);
|
||||||
|
DefaultSecurityConfigurationBootstrap.buildDefaultSignatureSigningConfiguration();
|
||||||
|
|
||||||
|
// Marshall and sign the object
|
||||||
|
XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(authnRequest).marshall(authnRequest);
|
||||||
|
Signer.signObject(signature);
|
||||||
|
|
||||||
|
return authnRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
|
||||||
|
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations);
|
||||||
|
OpenSaml4AuthenticationRequestResolver authenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(registrationResolver);
|
||||||
|
|
||||||
|
authenticationRequestResolver.setAuthnRequestCustomizer((context) -> {
|
||||||
|
// Set the required attributes
|
||||||
|
AuthnRequest authnRequest = context.getAuthnRequest();
|
||||||
|
authnRequest.setID("_" + UUID.randomUUID().toString()); // Add a unique ID
|
||||||
|
authnRequest.setVersion(SAMLVersion.VERSION_20); // Ensure version is 2.0
|
||||||
|
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI); // HTTP-POST
|
||||||
|
|
||||||
|
// Set Authentication Context
|
||||||
|
authnRequest.setRequestedAuthnContext(buildRequestedAuthnContext());
|
||||||
|
|
||||||
|
// Log the SAML AuthnRequest after setting context
|
||||||
|
String samlRequest = SamlRequestLogger.convertSAMLObjectToString(authnRequest);
|
||||||
|
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 X509Certificate readIdpCertificate() throws Exception {
|
||||||
|
// Path to your IDP public certificate PEM file
|
||||||
|
try (InputStream inStream = readKey("dev/saml/idp-certificate.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package net.gepafin.tendermanagement.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import net.gepafin.tendermanagement.entities.SamlResponseLogEntity;
|
||||||
|
import net.gepafin.tendermanagement.repositories.SamlResponseLogRepository;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SamlFailureHandler implements AuthenticationFailureHandler {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(SamlSuccessHandler.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SamlResponseLogRepository samlResponseLogRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
AuthenticationException exception) throws IOException {
|
||||||
|
try {
|
||||||
|
logger.error("SAML login failed: " + exception.getMessage());
|
||||||
|
|
||||||
|
// Log the failure details to the database (Optional)
|
||||||
|
SamlResponseLogEntity samlResponseLogEntity = new SamlResponseLogEntity();
|
||||||
|
samlResponseLogEntity.setRequest(request.toString());
|
||||||
|
samlResponseLogEntity.setResponse(response.toString());
|
||||||
|
samlResponseLogEntity.setExceptionObject(exception.toString());
|
||||||
|
samlResponseLogRepository.save(samlResponseLogEntity);
|
||||||
|
|
||||||
|
// Handle failure redirection
|
||||||
|
response.sendRedirect("http://gepafin-staging-fe.s3-website.eu-central-1.amazonaws.com/login");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error processing SAML failure handler", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package net.gepafin.tendermanagement.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import net.gepafin.tendermanagement.entities.SamlResponseLogEntity;
|
||||||
|
import net.gepafin.tendermanagement.repositories.SamlResponseLogRepository;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SamlSuccessHandler implements AuthenticationSuccessHandler{
|
||||||
|
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(SamlSuccessHandler.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SamlResponseLogRepository samlResponseLogRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Authentication authentication) throws IOException {
|
||||||
|
try {
|
||||||
|
// Cast the authentication object to Saml2Authentication
|
||||||
|
Saml2Authentication samlAuth = (Saml2Authentication) authentication;
|
||||||
|
Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) samlAuth.getPrincipal();
|
||||||
|
|
||||||
|
// Extract the user attributes from the principal
|
||||||
|
Map<String, List<Object>> userAttributes = principal.getAttributes();
|
||||||
|
|
||||||
|
// Log the user attributes for debugging purposes
|
||||||
|
logger.info("SAML User Attributes: " + userAttributes);
|
||||||
|
|
||||||
|
// Save the authentication details in the database (Optional)
|
||||||
|
SamlResponseLogEntity samlResponseLogEntity = new SamlResponseLogEntity();
|
||||||
|
samlResponseLogEntity.setAuthenticationObject(authentication.toString());
|
||||||
|
|
||||||
|
// Convert user attributes to JSON and save in DB
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
String userAttributesJson = objectMapper.writeValueAsString(userAttributes);
|
||||||
|
samlResponseLogEntity.setAuthenticationObject(userAttributesJson);
|
||||||
|
samlResponseLogRepository.save(samlResponseLogEntity);
|
||||||
|
|
||||||
|
// Successful login logic
|
||||||
|
logger.info("SAML login successful for user: " + principal.getName());
|
||||||
|
response.sendRedirect("http://gepafin-staging-fe.s3-website.eu-central-1.amazonaws.com/login");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error processing SAML success handler", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,39 +1,5 @@
|
|||||||
package net.gepafin.tendermanagement.config;
|
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 java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.apache.xml.security.Init;
|
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
|
||||||
import org.opensaml.core.config.InitializationService;
|
|
||||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
|
||||||
import org.opensaml.saml.common.SAMLVersion;
|
|
||||||
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.AuthnRequest;
|
|
||||||
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.opensaml.security.x509.BasicX509Credential;
|
|
||||||
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
|
|
||||||
import org.opensaml.xmlsec.signature.Signature;
|
|
||||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
|
||||||
import org.opensaml.xmlsec.signature.support.Signer;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -49,17 +15,6 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
|
|||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
|
||||||
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.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||||
@@ -69,63 +24,34 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|||||||
import org.springframework.web.filter.CorsFilter;
|
import org.springframework.web.filter.CorsFilter;
|
||||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
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.Components;
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
import io.swagger.v3.oas.models.servers.Server;
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import net.gepafin.tendermanagement.config.jwt.JWTFilter;
|
import net.gepafin.tendermanagement.config.jwt.JWTFilter;
|
||||||
import net.gepafin.tendermanagement.config.jwt.TokenProvider;
|
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
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity(prePostEnabled = true)
|
@EnableMethodSecurity(prePostEnabled = true)
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
|
|
||||||
private final TokenProvider tokenProvider;
|
private final TokenProvider tokenProvider;
|
||||||
|
private final SamlSuccessHandler samlSuccessHandler;
|
||||||
|
private final SamlFailureHandler samlFailureHandler;
|
||||||
|
|
||||||
@Value("${base-url}")
|
@Value("${base-url}")
|
||||||
String baseUrl;
|
String baseUrl;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SamlResponseLogRepository samlResponseLogRepository;
|
public SecurityConfig(TokenProvider tokenProvider, SamlSuccessHandler samlSuccessHandler, SamlFailureHandler samlFailureHandler) {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public SecurityConfig(TokenProvider tokenProvider) {
|
|
||||||
this.tokenProvider = tokenProvider;
|
this.tokenProvider = tokenProvider;
|
||||||
|
this.samlSuccessHandler =samlSuccessHandler;
|
||||||
|
this.samlFailureHandler=samlFailureHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void initXmlSecurity() throws Exception {
|
|
||||||
// Initialize Apache XML Security (Santuario)
|
|
||||||
Init.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void initOpenSAML() throws Exception {
|
|
||||||
InitializationService.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// @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
|
@Bean
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||||
return config.getAuthenticationManager();
|
return config.getAuthenticationManager();
|
||||||
@@ -186,55 +112,8 @@ public class SecurityConfig {
|
|||||||
* .saml2Login(saml -> saml.loginPage("/saml/login") // Entry point for SAML
|
* .saml2Login(saml -> saml.loginPage("/saml/login") // Entry point for SAML
|
||||||
* login .defaultSuccessUrl("/") // Redirect after successful SAML login );
|
* login .defaultSuccessUrl("/") // Redirect after successful SAML login );
|
||||||
*/
|
*/
|
||||||
.saml2Login(saml ->
|
.saml2Login(saml -> saml.defaultSuccessUrl("/").successHandler(samlSuccessHandler)
|
||||||
saml.defaultSuccessUrl("/")
|
.failureHandler(samlFailureHandler));
|
||||||
.successHandler((request, response, authentication) -> {
|
|
||||||
try {
|
|
||||||
// Cast the authentication object to Saml2Authentication
|
|
||||||
Saml2Authentication samlAuth = (Saml2Authentication) authentication;
|
|
||||||
Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) samlAuth.getPrincipal();
|
|
||||||
|
|
||||||
// Extract the user attributes from the principal
|
|
||||||
Map<String, List<Object>> userAttributes = principal.getAttributes();
|
|
||||||
|
|
||||||
// Log the user attributes for debugging purposes
|
|
||||||
logger.info("SAML User Attributes: " + userAttributes);
|
|
||||||
|
|
||||||
// Save the authentication details in the database (Optional)
|
|
||||||
SamlResponseLogEntity samlResponseLogEntity = new SamlResponseLogEntity();
|
|
||||||
samlResponseLogEntity.setAuthenticationObject(authentication.toString());
|
|
||||||
|
|
||||||
// Convert user attributes to JSON and save in DB
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
String userAttributesJson = objectMapper.writeValueAsString(userAttributes);
|
|
||||||
samlResponseLogEntity.setAuthenticationObject(userAttributesJson);
|
|
||||||
samlResponseLogRepository.save(samlResponseLogEntity);
|
|
||||||
|
|
||||||
// Successful login logic
|
|
||||||
logger.info("SAML login successful for user: " + principal.getName());
|
|
||||||
response.sendRedirect("http://gepafin-staging-fe.s3-website.eu-central-1.amazonaws.com/login");
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error processing SAML success handler", e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.failureHandler((request, response, exception) -> {
|
|
||||||
try {
|
|
||||||
logger.error("SAML login failed: " + exception.getMessage());
|
|
||||||
|
|
||||||
// Log the failure details to the database (Optional)
|
|
||||||
SamlResponseLogEntity samlResponseLogEntity = new SamlResponseLogEntity();
|
|
||||||
samlResponseLogEntity.setRequest(request.toString());
|
|
||||||
samlResponseLogEntity.setResponse(response.toString());
|
|
||||||
samlResponseLogEntity.setExceptionObject(exception.toString());
|
|
||||||
samlResponseLogRepository.save(samlResponseLogEntity);
|
|
||||||
|
|
||||||
// Handle failure redirection
|
|
||||||
response.sendRedirect("http://gepafin-staging-fe.s3-website.eu-central-1.amazonaws.com/login");
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error processing SAML failure handler", e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
@@ -250,148 +129,5 @@ public class SecurityConfig {
|
|||||||
.scheme("bearer").bearerFormat("JWT")));
|
.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)
|
|
||||||
.verificationX509Credentials(credentials -> {
|
|
||||||
try {
|
|
||||||
// Load the IDP's public certificate for verifying the SAML response signature
|
|
||||||
credentials.add(Saml2X509Credential.verification(readIdpCertificate()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthnRequest createSignedAuthnRequest(PrivateKey privateKey, X509Certificate certificate) throws Exception {
|
|
||||||
AuthnRequest authnRequest = (AuthnRequest) XMLObjectProviderRegistrySupport.getBuilderFactory()
|
|
||||||
.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME)
|
|
||||||
.buildObject(AuthnRequest.DEFAULT_ELEMENT_NAME);
|
|
||||||
|
|
||||||
authnRequest.setID("_" + UUID.randomUUID().toString());
|
|
||||||
authnRequest.setVersion(SAMLVersion.VERSION_20);
|
|
||||||
// authnRequest.setIssueInstant(new DateTime());
|
|
||||||
authnRequest.setIssueInstant(Instant.now());
|
|
||||||
|
|
||||||
|
|
||||||
// Sign the AuthnRequest
|
|
||||||
// BasicCredential signingCredential = new BasicCredential(certificate, privateKey);
|
|
||||||
BasicX509Credential signingCredential = new BasicX509Credential(certificate, privateKey);
|
|
||||||
|
|
||||||
Signature signature = (Signature) XMLObjectProviderRegistrySupport.getBuilderFactory()
|
|
||||||
.getBuilder(Signature.DEFAULT_ELEMENT_NAME)
|
|
||||||
.buildObject(Signature.DEFAULT_ELEMENT_NAME);
|
|
||||||
|
|
||||||
signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
|
|
||||||
signature.setSigningCredential(signingCredential);
|
|
||||||
signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); // Set RSA-SHA1
|
|
||||||
|
|
||||||
authnRequest.setSignature(signature);
|
|
||||||
DefaultSecurityConfigurationBootstrap.buildDefaultSignatureSigningConfiguration();
|
|
||||||
|
|
||||||
// Marshall and sign the object
|
|
||||||
XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(authnRequest).marshall(authnRequest);
|
|
||||||
Signer.signObject(signature);
|
|
||||||
|
|
||||||
return authnRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
|
|
||||||
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations);
|
|
||||||
OpenSaml4AuthenticationRequestResolver authenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(registrationResolver);
|
|
||||||
|
|
||||||
authenticationRequestResolver.setAuthnRequestCustomizer((context) -> {
|
|
||||||
// Set the required attributes
|
|
||||||
AuthnRequest authnRequest = context.getAuthnRequest();
|
|
||||||
authnRequest.setID("_" + UUID.randomUUID().toString()); // Add a unique ID
|
|
||||||
authnRequest.setVersion(SAMLVersion.VERSION_20); // Ensure version is 2.0
|
|
||||||
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI); // HTTP-POST
|
|
||||||
|
|
||||||
// Set Authentication Context
|
|
||||||
authnRequest.setRequestedAuthnContext(buildRequestedAuthnContext());
|
|
||||||
|
|
||||||
// Log the SAML AuthnRequest after setting context
|
|
||||||
String samlRequest = SamlRequestLogger.convertSAMLObjectToString(authnRequest);
|
|
||||||
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 X509Certificate readIdpCertificate() throws Exception {
|
|
||||||
// Path to your IDP public certificate PEM file
|
|
||||||
try (InputStream inStream = readKey("dev/saml/idp-certificate.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user