I'm trying to add Tomcat container security to a Spring Boot application (to take advantage of some legacy custom valves). With the snippet below (starting from the example at https://github.com/spring-guides/gs-rest-service.git) I expected basic authentication would kick in, but it doesn't. Tried with both Spring Boot 1.1.6 and 1.1.7.
The same configuration when starting an embedded Tomcat "manually" (without involving Boot at all) works as expected.
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer factory) {
if(factory instanceof TomcatEmbeddedServletContainerFactory){
TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) factory;
containerFactory.addContextCustomizers(new TomcatContextCustomizer() {
#Override
public void customize(Context context) {
LoginConfig config = new LoginConfig();
config.setAuthMethod("BASIC");
context.setLoginConfig(config);
context.addSecurityRole("admin");
SecurityConstraint constraint = new SecurityConstraint();
constraint.addAuthRole("admin");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
constraint.addCollection(collection);
context.addConstraint(constraint);
}
});
}
}
};
}
Related
I have created a sample project which can demonstrate SAML 2 SSO capabilities with saml providers such as Azure AD and Okta.
I was able to configure both of above providers at once in spring configuration by using RelyingPartyRegistrationRepository and both of them are working as expected.
#Bean
protected RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
RelyingPartyRegistration oktaRegistration = RelyingPartyRegistrations.fromMetadataLocation("https://trial-27.okta.com/app/e/sso/saml/metadata").registrationId("okta").build();
RelyingPartyRegistration azureRegistration = RelyingPartyRegistrations.fromMetadataLocation("file:D:\\saml-test-5.xml").registrationId("azure-saml-test").build();
List<RelyingPartyRegistration> registrationList = new ArrayList<>();
registrationList.add(oktaRegistration);
registrationList.add(azureRegistration);
return new InMemoryRelyingPartyRegistrationRepository(registrationList);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize ->
authorize.antMatchers("/").permitAll().anyRequest().authenticated()
).saml2Login();
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrations());
Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver());
http.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
return http.build();
}
I would like to know whether there is any way to create RelyingPartyRegistrationRepository dynamically once the application fully started. The requirement is to take the SAML metadata file from user in some sort of a form upload and then create RelyingPartyRegistrationRepository based on it.
The issue is, RelyingPartyRegistrationRepository is a Spring bean which is used by the Spring security internals. In this case even though we could create new RelyingPartyRegistrationRepository instances, will Spring security take them dynamically?
You will not create multiple RelyingPartyRegistrationRepository, you will create your custom implementation of RelyingPartyRegistrationRepository that accepts adding new entries to it. A simple example:
#Service
public class MyRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository {
private final List<RelyingPartyRegistration> registrations = new ArrayList<>();
public MyRelyingPartyRegistrationRepository() {
addDefaultRegistrations();
}
private void addDefaultRegistrations() {
RelyingPartyRegistration oktaRegistration = RelyingPartyRegistrations.fromMetadataLocation("https://trial-27.okta.com/app/e/sso/saml/metadata").registrationId("okta").build();
RelyingPartyRegistration azureRegistration = RelyingPartyRegistrations.fromMetadataLocation("file:D:\\saml-test-5.xml").registrationId("azure-saml-test").build();
add(oktaRegistration);
add(azureRegistration);
}
#Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
for (RelyingPartyRegistration registration : this.registrations) {
if (registration.getRegistrationId().equals(registrationId)) {
return registration;
}
}
return null;
}
public void add(RelyingPartyRegistration newRegistration) {
this.registrations.add(newRegistration);
}
}
And then in a Controller, for example, you can autowire this dependency and add new registrations to it:
#RestController
public class SamlController {
private final MyRelyingPartyRegistrationRepository repository;
#PostMapping("/registration")
public void addRegistration(/* receive it somehow */) {
this.repository.add(theRegistration);
}
}
Referring to this SO answer, I'd like to setup the equivalent of this web.xml configuration in a JSF / JoinFaces / SpringBoot application (that doesn't have web.xml).
<env-entry>
<env-entry-name>jsf/ClientSideSecretKey</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>[AES key in Base64 format]</env-entry-value>
</env-entry>
Any pointers?
If you are using spring boot and embedded tomcat server, you can add <env-entry> programmatically with the following configuration.
#SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
// adding <resource-ref>
ContextResource resource = new ContextResource();
resource.setName("jdbc/myJndiResource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "org.postgresql.Driver");
resource.setProperty("url", "jdbc:postgresql://hostname:port/dbname");
resource.setProperty("username", "username");
resource.setProperty("password", "password");
context.getNamingResources()
.addResource(resource);
// adding <env-entry>
ContextEnvironment ce = new ContextEnvironment();
ce.setName("jsf/ClientSideSecretKey");
ce.setType(String.class.getName());
ce.setValue("[AES key in Base64 format]");
context.getNamingResources().addEnvironment(ce);
}
};
}
public static void main(String[] args) throws NamingException {
SpringApplication.run(DemoApplication.class, args);
}
}
Once defined the jndi naming resources they can be accessed in your application using JndiTemplate of InitialContext.
JndiTemplate jndiTemplate = new JndiTemplate();
String str = (String) jndiTemplate.lookup("java:comp/env/jsf/ClientSideSecretKey");
Hope this helps you in resolving your problem.
Essentially <env-entry> declares a web application context attribute.
You can initialize your servlet context and provide the equivalent servlet context attributes in your Spring Boot application.
For that purpose, you can register a bean that implements the ServletContextInitializer interface (or WebApplicationInitializer if your app has to be deployed in a traditional servlet container). For example:
public class JsfServletContextInitializer implements ServletContextInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute("jsf/ClientSideSecretKey", "[AES key in Base64 format]");
}
}
Do not forget to register it as a bean in your configuration.
I'm trying to set max-swallow-size property of tomcat to -1 in springboot microservice while upgrading to springboot version 2; my earlier code was working but in upgrade some classes have changed so it stopped working.
I tried to set property in two ways but both are not working;
with service configuration
#Bean
public ServletWebServerFactory servletContainerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
if(connector.getProtocolHandler() instanceof AbstractHttp11Protocol) {
logger.debug("Setting maxSwallowSize for server connector as "+maxSwallowSize);
((AbstractHttp11Protocol <?>) connector.getProtocolHandler()).setMaxSwallowSize(maxSwallowSize);
}
}
});
return factory;
}
In control flow, I can see the debug line printed but it have not taken effect as end -point response is 502(Bad gateway) instead of 400
Second way :
2. through application.properties file with property
server.tomcat.max-swallow-size=-1
This is also not honored.
Now, How can I verify the property value whether it is actually set or not ?
or Am I setting the value in correct way ?
This New class has resolved my issue
#Component
public class TomcatCustomizer implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
if(connector.getProtocolHandler() instanceof AbstractHttp11Protocol) {
((AbstractHttp11Protocol <?>) connector.getProtocolHandler()).setMaxSwallowSize(maxSwallowSize);
}
}
});
}
}
and I have used updated properties in spring boot 2.0
spring.servlet.multipart.max-file-size= XX MB
spring.servlet.multipart.max-request-size= YY MB
I am trying to configure HTTP 2 for my Spring Boot project that runs in the Embedded Tomcat Server 8.5.34 but I am unable to do so. I have followed this SO Thread but couldn't achieve that. My Server starts and is working fine but it works in HTTP 1.1 even after configuring for HTTP 2. I have configured my project to run with a self signed certificate and those setting are in the application.properties file. Below is my configuration file. Please help.
#Configuration
public class ConnectorConfig {
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(getHttpConnector());
// the following line isn't working
// tomcat.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> connector.addUpgradeProtocol(new Http2Protocol()));
return tomcat;
}
#Bean // not working
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
return (container) -> {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
((TomcatEmbeddedServletContainerFactory) container)
.addConnectorCustomizers((connector) -> connector.addUpgradeProtocol(new Http2Protocol()));
}
};
}
private Connector getHttpConnector() {
Connector connector = new Connector(TomcatEmbeddedServletContainerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(9000);
// the following line isn't working
// connector.addUpgradeProtocol(new Http2Protocol());
return connector;
}
}
I need to add an AJP connector to embedded Tomcat and disable (or replace) the default connector that listens on 8080.
I've tried customizing this with EmbeddedServletContainerCustomizer, but I can't get a handle on the Tomcat object to replace the default connector created there. As a result I end up with the http port on 8080 in addition to my AJP ports.
Next, I've tried extending TomcatEmbeddedServletContainerFactory and overriding its getTomcatEmbeddedServletContainer method. Per the JavaDoc, this appears to be the perfect place to replace the default connector, but it still ends up being enabled (and doesn't create my AJP connector either). Any ideas what I might be missing? I've verified with the debugger that my configuration is being run.
Per answer below, here's the cleanest solution:
#Bean
public EmbeddedServletContainerFactory tomcat() {
TomcatEmbeddedServletContainerFactory myFactory = new TomcatEmbeddedServletContainerFactory();
myFactory.setProtocol("AJP/1.3");
myFactory.setPort(9000);
return myFactory;
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer2() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setRedirectPort(9001);
}
});
}
};
}
You can use a TomcatConnectorCustomizer to configure the existing connector to use AJP by adding it to the TomcatEmbeddedServletContainerFactory.
Just create a EmbeddedServletContainerCustomizer bean and reconfigure it to AJP:
#Configuration
public class ServletConfig {
// AJP port defined in properties (default 666)
#Value("${tomcat.ajp.port:666}")
private Integer ajpPort;
#Bean
public EmbeddedServletContainerCustomizer ajpContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.setProtocol("AJP/1.3");
tomcat.setPort(ajpPort);
}
};
}
}