package mujina.idp;
import mujina.api.IdpConfiguration;
import mujina.saml.KeyStoreLocator;
import mujina.saml.ProxiedSAMLContextProviderLB;
import org.opensaml.common.binding.decoding.URIComparator;
import org.opensaml.common.binding.security.IssueInstantRule;
import org.opensaml.common.binding.security.MessageReplayRule;
import org.opensaml.saml2.binding.decoding.HTTPPostDecoder;
import org.opensaml.saml2.binding.decoding.HTTPRedirectDeflateDecoder;
import org.opensaml.saml2.binding.encoding.HTTPPostSimpleSignEncoder;
import org.opensaml.util.storage.MapBasedStorageService;
import org.opensaml.util.storage.ReplayCache;
import org.opensaml.ws.security.provider.BasicSecurityPolicy;
import org.opensaml.ws.security.provider.StaticSecurityPolicyResolver;
import org.opensaml.xml.parse.StaticBasicParserPool;
import org.opensaml.xml.parse.XMLParserException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.saml.SAMLBootstrap;
import org.springframework.security.saml.context.SAMLContextProvider;
import org.springframework.security.saml.key.JKSKeyManager;
import org.springframework.security.saml.util.VelocityFactory;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Collections;
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebMvcConfigurerAdapter {
@Autowired
private Environment environment;
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
@Bean
@Autowired
public SAMLMessageHandler samlMessageHandler(@Value("${idp.clock_skew}") int clockSkew,
@Value("${idp.expires}") int expires,
@Value("${idp.base_url}") String idpBaseUrl,
@Value("${idp.compare_endpoints}") boolean compareEndpoints,
IdpConfiguration idpConfiguration,
JKSKeyManager keyManager)
throws NoSuchAlgorithmException, CertificateException, InvalidKeySpecException, KeyStoreException, IOException, XMLStreamException, XMLParserException, URISyntaxException {
StaticBasicParserPool parserPool = new StaticBasicParserPool();
BasicSecurityPolicy securityPolicy = new BasicSecurityPolicy();
securityPolicy.getPolicyRules().addAll(Arrays.asList(new IssueInstantRule(clockSkew, expires),
new MessageReplayRule(new ReplayCache(new MapBasedStorageService(), 14400000))));
HTTPRedirectDeflateDecoder httpRedirectDeflateDecoder = new HTTPRedirectDeflateDecoder(parserPool);
HTTPPostDecoder httpPostDecoder = new HTTPPostDecoder(parserPool);
if (!compareEndpoints) {
URIComparator noopComparator = (uri1, uri2) -> true;
httpPostDecoder.setURIComparator(noopComparator);
httpRedirectDeflateDecoder.setURIComparator(noopComparator);
}
parserPool.initialize();
HTTPPostSimpleSignEncoder httpPostSimpleSignEncoder = new HTTPPostSimpleSignEncoder(VelocityFactory.getEngine(), "/templates/saml2-post-simplesign-binding.vm", true);
return new SAMLMessageHandler(
keyManager,
Arrays.asList(httpRedirectDeflateDecoder, httpPostDecoder),
httpPostSimpleSignEncoder,
new StaticSecurityPolicyResolver(securityPolicy),
idpConfiguration,
idpBaseUrl);
}
@Bean
public static SAMLBootstrap sAMLBootstrap() {
return new SAMLBootstrap();
}
@Autowired
@Bean
public JKSKeyManager keyManager(@Value("${idp.entity_id}") String idpEntityId,
@Value("${idp.private_key}") String idpPrivateKey,
@Value("${idp.certificate}") String idpCertificate,
@Value("${idp.passphrase}") String idpPassphrase) throws InvalidKeySpecException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, XMLStreamException {
KeyStore keyStore = KeyStoreLocator.createKeyStore(idpPassphrase);
KeyStoreLocator.addPrivateKey(keyStore, idpEntityId, idpPrivateKey, idpCertificate, idpPassphrase);
return new JKSKeyManager(keyStore, Collections.singletonMap(idpEntityId, idpPassphrase), idpEntityId);
}
@Bean
public ServletContextInitializer servletContextInitializer() {
//otherwise the two localhost instances override each other session
return servletContext -> servletContext.getSessionCookieConfig().setName("mujinaIdpSessionId");
}
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private IdpConfiguration idpConfiguration;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/metadata", "/favicon.ico", "/api/**", "/*.css").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().hasRole("USER")
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.failureUrl("/login?error=true")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/");
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new AuthenticationProvider(idpConfiguration));
}
}
}