From 398fb7cc43a7aee1ffe4d67185d879549aebfbec Mon Sep 17 00:00:00 2001 From: rajesh Date: Sun, 22 Sep 2024 13:23:32 +0530 Subject: [PATCH 1/2] created an api to get metadata --- pom.xml | 36 ++++++++++++++++--- .../web/rest/api/SamlApi.java | 30 ++++++++++++++++ .../web/rest/api/impl/SamlApiController.java | 33 +++++++++++++++++ src/main/resources/application-dev.properties | 3 +- .../resources/application-local.properties | 3 +- .../resources/application-testing.properties | 3 +- 6 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/web/rest/api/SamlApi.java create mode 100644 src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/SamlApiController.java diff --git a/pom.xml b/pom.xml index 713de99a..ad01e409 100644 --- a/pom.xml +++ b/pom.xml @@ -116,10 +116,6 @@ jjwt-jackson 0.11.5 - - org.springframework.boot - spring-boot-starter-web - jakarta.validation jakarta.validation-api @@ -139,6 +135,37 @@ problem-spring-web 0.23.0 + + + + org.springframework.security + spring-security-saml2-service-provider + + + + + org.opensaml + opensaml-core + 4.0.1 + + + + + + org.opensaml + opensaml-saml-api + 4.0.1 + + + + + + org.opensaml + opensaml-saml-impl + 4.0.1 + + + @@ -152,7 +179,6 @@ org.liquibase liquibase-maven-plugin - 4.20.0 src/main/resources/application.properties diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/SamlApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/SamlApi.java new file mode 100644 index 00000000..54ac52dd --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/SamlApi.java @@ -0,0 +1,30 @@ +package net.gepafin.tendermanagement.web.rest.api; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.web.rest.api.errors.ErrorConstants; + +public interface SamlApi { + + + @Operation(summary = "Api to get SP metadata", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE) })), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE) })) }) + @GetMapping(value = "/gw/metadata", + produces = { "application/json" }) + ResponseEntity getMetadata(HttpServletRequest request); + +} diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/SamlApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/SamlApiController.java new file mode 100644 index 00000000..b9a35be8 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/SamlApiController.java @@ -0,0 +1,33 @@ +package net.gepafin.tendermanagement.web.rest.api.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver; +import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.web.rest.api.SamlApi; + +@RestController +@RequestMapping("${openapi.gepafin.base-path:/v1/saml}") +public class SamlApiController implements SamlApi{ + + @Autowired + private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; + + @Override + public ResponseEntity getMetadata(HttpServletRequest request) { + Saml2MetadataResolver metadataResolver = new OpenSamlMetadataResolver(); + RelyingPartyRegistration registration = relyingPartyRegistrationRepository.findByRegistrationId("loginumbria"); + return ResponseEntity.status(HttpStatus.OK).header("Content-Type", MediaType.APPLICATION_XML_VALUE) + .body(metadataResolver.resolve(registration)); + + } + +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 72135766..c160287a 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -5,4 +5,5 @@ spring.datasource.password=vs1pAc9vu07mMcdx93j6WiBS spring.datasource.driver-class-name=org.postgresql.Driver # JPA Configuration -spring.h2.console.enabled=true \ No newline at end of file +spring.h2.console.enabled=true +base-url=https://api-dev-gepafin.memento.credit \ No newline at end of file diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 7ed5944b..6b925e03 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -5,4 +5,5 @@ spring.datasource.password=root spring.datasource.driver-class-name=org.postgresql.Driver # JPA Configuration -spring.jpa.show-sql=true \ No newline at end of file +spring.jpa.show-sql=true +base-url=http://localhost:8080 \ No newline at end of file diff --git a/src/main/resources/application-testing.properties b/src/main/resources/application-testing.properties index ea3a5732..12b95acb 100644 --- a/src/main/resources/application-testing.properties +++ b/src/main/resources/application-testing.properties @@ -4,4 +4,5 @@ spring.datasource.username=sa spring.datasource.password=sa # JPA Configuration -spring.h2.console.enabled=true \ No newline at end of file +spring.h2.console.enabled=true +base-url=http://localhost:8080 \ No newline at end of file From d2df445edf5afc136e1123ce07b9d7c436111fd2 Mon Sep 17 00:00:00 2001 From: rajesh Date: Mon, 23 Sep 2024 13:31:02 +0530 Subject: [PATCH 2/2] updated code --- .../config/SecurityConfig.java | 186 +++++++++++++++--- .../entities/SamlResponseLogEntity.java | 26 +++ .../SamlResponseLogRepository.java | 11 ++ .../web/rest/api/impl/SamlApiController.java | 6 + src/main/resources/application.properties | 1 + .../db/changelog/db.changelog-1.0.0.xml | 26 +++ 6 files changed, 232 insertions(+), 24 deletions(-) create mode 100644 src/main/java/net/gepafin/tendermanagement/entities/SamlResponseLogEntity.java create mode 100644 src/main/java/net/gepafin/tendermanagement/repositories/SamlResponseLogRepository.java diff --git a/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java b/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java index e06c3e4b..22ac7dff 100644 --- a/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java +++ b/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java @@ -1,6 +1,18 @@ package net.gepafin.tendermanagement.config; - +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.InputStream; +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.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; @@ -14,6 +26,11 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt 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.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; @@ -23,6 +40,8 @@ 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; @@ -30,24 +49,29 @@ 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; @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 AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } - @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -75,8 +99,8 @@ public class SecurityConfig { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("*"); - config.addAllowedMethod("*"); - config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + config.addAllowedHeader("*"); config.setMaxAge(3600l); if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) { @@ -86,24 +110,91 @@ public class SecurityConfig { } return new CorsFilter(source); } - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception { - http - .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests(auth -> auth - .requestMatchers(mvc.pattern(HttpMethod.POST, "/v1/user/login")).permitAll() - .requestMatchers("/swagger-ui/**").permitAll() - .requestMatchers("/v1/api-docs/**").permitAll() - .anyRequest().authenticated() - ) - .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ) + 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); + .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(); } @@ -116,4 +207,51 @@ public class SecurityConfig { 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(false) + ) + .build(); + return new InMemoryRelyingPartyRegistrationRepository(registration); + } + + + + public PrivateKey readPrivateKey() throws Exception { + // Path to your private key PEM file + File privateKeyFile = new File("src/main/resources/dev/saml/private-key.pem"); + try (PemReader pemReader = new PemReader(new FileReader(privateKeyFile))) { + // 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 + File certFile = new File("src/main/resources/dev/saml/public-cert.pem"); + try (InputStream inStream = new FileInputStream(certFile)) { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certFactory.generateCertificate(inStream); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/entities/SamlResponseLogEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/SamlResponseLogEntity.java new file mode 100644 index 00000000..53e262e9 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/entities/SamlResponseLogEntity.java @@ -0,0 +1,26 @@ +package net.gepafin.tendermanagement.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Data; + +@Entity +@Table(name = "SAML_RESPONSE_LOG") +@Data +public class SamlResponseLogEntity extends BaseEntity{ + + @Column(name = "REQUEST") + private String request; + + @Column(name = "RESPONSE") + private String response; + + @Column(name = "AUTHENTICATION_OBJECT") + private String authenticationObject; + + @Column(name = "EXCEPTION_OBJECT") + private String exceptionObject; + + +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/SamlResponseLogRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/SamlResponseLogRepository.java new file mode 100644 index 00000000..3f98e083 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/repositories/SamlResponseLogRepository.java @@ -0,0 +1,11 @@ +package net.gepafin.tendermanagement.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import net.gepafin.tendermanagement.entities.SamlResponseLogEntity; + +@Repository +public interface SamlResponseLogRepository extends JpaRepository { + +} diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/SamlApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/SamlApiController.java index b9a35be8..f49b9fa5 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/SamlApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/SamlApiController.java @@ -1,5 +1,7 @@ package net.gepafin.tendermanagement.web.rest.api.impl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -12,17 +14,21 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; +import net.gepafin.tendermanagement.config.SecurityConfig; import net.gepafin.tendermanagement.web.rest.api.SamlApi; @RestController @RequestMapping("${openapi.gepafin.base-path:/v1/saml}") public class SamlApiController implements SamlApi{ + private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class); + @Autowired private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; @Override public ResponseEntity getMetadata(HttpServletRequest request) { + logger.info("get SP metadata"); Saml2MetadataResolver metadataResolver = new OpenSamlMetadataResolver(); RelyingPartyRegistration registration = relyingPartyRegistrationRepository.findByRegistrationId("loginumbria"); return ResponseEntity.status(HttpStatus.OK).header("Content-Type", MediaType.APPLICATION_XML_VALUE) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 07fcf32f..1b6634c5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -35,6 +35,7 @@ aws.s3.url.folder=gepafin # Ensure these values match your expectations security.authentication.jwt.secret=my-secret-token-to-change-in-prod-environment-your-super-secure-randomly-generated-key security.authentication.jwt.token-validity-in-seconds=86400 +base-url=https://api-dev-gepafin.memento.credit spring.main.allow-circular-references=true diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index f4a78969..d7377f5c 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -710,4 +710,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +