324 lines
17 KiB
Java
324 lines
17 KiB
Java
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;
|
|
}
|
|
} |