49

For Spring Boot based application I have configurared ssl properties at application.properties, see my configuration here:

server.port=8443
server.ssl.key-alias=tomcat
server.ssl.key-password=123456
server.ssl.key-store=classpath:key.p12
server.ssl.key-store-provider=SunJSSE
server.ssl.key-store-type=pkcs12

And I have added conection at Application.class, like

@Bean
public EmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
    final TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
    factory.addAdditionalTomcatConnectors(this.createConnection());
    return factory;
}

private Connector createConnection() {
    final String protocol = "org.apache.coyote.http11.Http11NioProtocol";
    final Connector connector = new Connector(protocol);

    connector.setScheme("http");
    connector.setPort(9090);
    connector.setRedirectPort(8443);
    return connector;
}

But when I try the following by

http://127.0.0.1:9090/

redirect to

https://127.0.0.1:8443/

is not performed. Who faced a similar problem?

fap
  • 663
  • 1
  • 5
  • 14
Arseniy Ulakaiev
  • 545
  • 1
  • 5
  • 9

9 Answers9

40

For Tomcat to perform a redirect, you need to configure it with one or more security constraints. You can do this by post-processing the Context using a TomcatEmbeddedServletContainerFactory subclass.

For example:

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);
    }
};

Due to CONFIDENTIAL and /*, this will cause Tomcat to redirect every request to HTTPS. You can configure multiple patterns and multiple constraints if you need more control over what is and is not redirected.

An instance of the above TomcatEmbeddedServletContainerFactory subclass should be defined as a bean using a @Bean method in a @Configuration class.

Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
  • 8
    is there a similar approach for Jetty? – checketts Sep 25 '15 at 23:25
  • 1
    What is the appropriate place for this code snippet? – Jelle den Burger Nov 04 '16 at 14:21
  • 1
    As shown in the question, the `TomcatEmbeddedServletContainerFactory` should be exposed as a bean in a configuration class. – Andy Wilkinson Nov 04 '16 at 17:14
  • 2
    `POST` requests are not redirected. – nurgasemetey Oct 15 '17 at 20:46
  • This solution work only for 1 application server: tomcat. – Oleksii Kyslytsyn Oct 26 '17 at 21:19
  • 3
    @Oleksii Yes, that's right. The question was specifically about Tomcat so that's what the answer addresses. If you're interested in a different embedded server (and aren't using Spring Security so the other answers here don't apply) you should ask another question that's specific to the server you are using. – Andy Wilkinson Oct 27 '17 at 08:07
  • I do not mind the answer. The comment is about that this solution is so narrow, that it touches only one application server tomcat and it is not uniform in that context for other application servers. In the bottom there is a more uniform answer than this accepted one. – Oleksii Kyslytsyn Oct 27 '17 at 10:30
  • What file is this in? Where do I add this so that it works? – Andrew Koper May 04 '21 at 16:11
  • @AndrewKoper That's already been asked and answered in some earlier comments. I've also edited the answer to hopefully make things a bit clearer. – Andy Wilkinson May 04 '21 at 17:16
  • For spring boot 2 and above see this post https://stackoverflow.com/questions/47700115/tomcatembeddedservletcontainerfactory-is-missing-in-spring-boot-2 – arVahedi Nov 13 '21 at 19:48
  • How do you work with this solution during local development? For example, on my server I have the TLS certificate all configured and working but on my local machine, I don't need this redirect logic to happen. Are we supposed to put conditional logic for the connector os is there a better practice? – motopascyyy Jan 10 '23 at 22:11
  • @motopascyyy I would make the `TomcatEmbeddedServletContainerFactory` a `@Bean` and use `@Profile` so that it's only defined when a particular profile is active. You'd then activate that profile when you want SSL to be enabled. – Andy Wilkinson Jan 11 '23 at 12:42
34

Setting this property on your application*.properties file (and the corresponding servlet-specific configuration for HTTPS headers in case you are running behind a proxy) and having Spring Security set-up (e.g. having org.springframework.boot:spring-boot-starter-security on your classpath) should be enough:

security.require-ssl=true

Now, for some reason that configuration is not honored when basic authentication is disabled (at least on old versions of Spring Boot). So in that case you would need to take an extra step and honor it yourself by manually configuring the security on your code, like this:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Inject private SecurityProperties securityProperties;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        if (securityProperties.isRequireSsl()) http.requiresChannel().anyRequest().requiresSecure();
    }
}

So, in case you are using Tomcat behind a proxy, you would have all these properties on your application*.properties file:

security.require-ssl=true

server.tomcat.remote_ip_header=x-forwarded-for
server.tomcat.protocol_header=x-forwarded-proto
Rodrigo Quesada
  • 1,460
  • 1
  • 14
  • 20
19

In Spring-Boot, need below dependency

Step 1-

<dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Step 2- Just need to do below configurations on application.properties file

 - server.port=8443
 - server.ssl.key.alias=ode-https
 - server.ssl.key-store-type=JKS (just for testing i USED JSK, but for production normally use pkcs12)
 - server.ssl.key-password=password
 - server.ssl.key-store=classpath:ode-https.jks

Step 3- now need to generate a certificate using the above details.

keytool -genkey -alias ode-https -storetype JKS -keyalg RSA -keys ize 2048 -validity 365 -keystore ode-https.jks

Step 4- move the certificate to resources folder in your program.

Step 5- Create config class

@Configuration
public class HttpsConfiguration {
    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
            @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(redirectConnector());
        return tomcat;
    }

    @Value("${server.port.http}") //Defined in application.properties file
    int httpPort;

    @Value("${server.port}") //Defined in application.properties file
    int httpsPort;

    private Connector redirectConnector() {
        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setScheme("http");
        connector.setPort(httpPort);
        connector.setSecure(false);
        connector.setRedirectPort(httpsPort);
        return connector;
    }
}

that's it.

Rene Larsen
  • 1,178
  • 9
  • 16
Arvind Pant
  • 401
  • 5
  • 9
16

Follow only 2 steps.

1- Add spring security dependency in pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

2- Add this class on root package of your application.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requiresChannel().anyRequest().requiresSecure();
    }
}
rogue lad
  • 2,413
  • 2
  • 29
  • 32
  • awesome. works great when behind ALB. might also add `http.authorizeRequests().antMatchers("/**").permitAll()` if not using any other features of spring security – Jpnh May 07 '18 at 09:01
  • This is the solution to "The security auto-configuration is no longer customizable. Provide your own WebSecurityConfigurer bean instead." message when using `security.require-ssl=true`. Thank You! :) **Note**: don't use `org.springframework.security spring-security-config` since that will cause `ClassNotFound` exceptions for some security classes. – Aleksandar May 08 '18 at 13:50
  • I am getting following exception with with above code - 2019-12-04 14:10:55.264 INFO 12236 --- [nio-8080-exec-5] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:415) ~[tomcat-embed-core-9.0.27.jar:9.0.27] at – Priyanka Chaurishia Dec 04 '19 at 22:12
  • @PriyankaChaurishia Please rename your controller HTTP method to a valid name. – rogue lad Dec 05 '19 at 22:33
14

The approved answer was not enough for me.

I had to also add the following to my web security config, as I am not using the default 8080 port:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private Environment environment;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // other security configuration missing

        http.portMapper()
                .http(Integer.parseInt(environment.getProperty("server.http.port"))) // http port defined in yml config file
                .mapsTo(Integer.parseInt(environment.getProperty("server.port"))); // https port defined in yml config file

        // we only need https on /auth
        http.requiresChannel()
                .antMatchers("/auth/**").requiresSecure()
                .anyRequest().requiresInsecure();
    }
}
Alex Burdusel
  • 3,015
  • 5
  • 38
  • 49
  • 3
    Just as an alternative, you could also use: `@Value("${server.http.port}") private int httpPort;` and `@Value("${server.port}") private int httpsPort;` for fields, then you wouldn't have to parseInt, and the code would (imo) look a bit cleaner. :) – Raid Sep 01 '20 at 08:43
5

Since TomcatEmbeddedServletContainerFactory has been removed in Spring Boot 2, use this:

@Bean
public TomcatServletWebServerFactory httpsRedirectConfig() {
    return new TomcatServletWebServerFactory () {
        @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);
        }
    };
}
Mahozad
  • 18,032
  • 13
  • 118
  • 133
5
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
        
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    
    http.requiresChannel().anyRequest().requiresSecure();   
  }
}

If your application is behind a load balancer or reverse proxy server you will need to add the following to your application.properties file:

server.forward-headers-strategy=NATIVE

This will prevent an redirect loop.

If you are using Tomcat you can configure the names of the forward headers in your application.properties file:

server.tomcat.remote_ip_header=x-forwarded-for 
server.tomcat.protocol_header=x-forwarded-proto

See the Spring Boot Documentation for more information.

Vlad Troyan
  • 147
  • 3
  • 4
  • 1
    One thing you forgot to mention is that the above requires `implementation 'org.springframework.boot:spring-boot-starter-security'` to be declared (or it's Maven equivalent) – anthonymonori Apr 22 '20 at 16:03
  • This answer tell you how to *enforce* https-only connections, but not how to redirect, no? – FerdTurgusen Sep 06 '21 at 17:11
3

For Jetty (tested with 9.2.14), you need to add an extra configuration to the WebAppContext (adjust the pathSpec to your taste) :

import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;

class HttpToHttpsJettyConfiguration extends AbstractConfiguration
{
    // http://wiki.eclipse.org/Jetty/Howto/Configure_SSL#Redirecting_http_requests_to_https
    @Override
    public void configure(WebAppContext context) throws Exception
    {
        Constraint constraint = new Constraint();
        constraint.setDataConstraint(2);

        ConstraintMapping constraintMapping = new ConstraintMapping();
        constraintMapping.setPathSpec("/*");
        constraintMapping.setConstraint(constraint);

        ConstraintSecurityHandler constraintSecurityHandler = new ConstraintSecurityHandler();
        constraintSecurityHandler.addConstraintMapping(constraintMapping);

        context.setSecurityHandler(constraintSecurityHandler);
    }
}

Then wire this class by adding an @Configuration class implementing EmbeddedServletContainerCustomizer along with a new Connector that listen to the non secure port :

@Configuration
public class HttpToHttpsJettyCustomizer implements EmbeddedServletContainerCustomizer
{
    @Override
    public void customize(ConfigurableEmbeddedServletContainer container)
    {
        JettyEmbeddedServletContainerFactory containerFactory = (JettyEmbeddedServletContainerFactory) container;
        //Add a plain HTTP connector and a WebAppContext config to force redirect from http->https
        containerFactory.addConfigurations(new HttpToHttpsJettyConfiguration());

        containerFactory.addServerCustomizers(server -> {
            HttpConfiguration http = new HttpConfiguration();
            http.setSecurePort(443);
            http.setSecureScheme("https");

            ServerConnector connector = new ServerConnector(server);
            connector.addConnectionFactory(new HttpConnectionFactory(http));
            connector.setPort(80);

            server.addConnector(connector);
        });
    }
}

This implies that the SSL Connector is already configured and listening on port 443 in this example.

jebeaudet
  • 1,533
  • 16
  • 15
1

Using an Interceptor to send off a redirect to https://

(does not require Spring Security)

These all seem way complicated. Why don't we just add an interceptor that checks the port and if it's port 80, redirect it to the same url, but prefixed with https:// instead.

@Component
public class HttpsConfig implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // String requestedPort = request.getServerPort() if you're not behind a proxy
        String requestedPort = request.getHeader("X-Forwarded-Port"); // I'm behind a proxy on Heroku

        if (requestedPort != null && requestedPort.equals("80")) { // This will still allow requests on :8080
            response.sendRedirect("https://" + request.getServerName() + request.getRequestURI() + (request.getQueryString() != null ? "?" + request.getQueryString() : ""));
            return false;
        }
        return true;
    }

}

and don't forget to register your lovely interceptor

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HttpsConfig());
    }
}

Note: Your production web server will typically run on 1 or 2 ports (80 unsecure 443 secure), and you should know what they are, so I don't see this as much of a security risk allowing other ports.

sparkyspider
  • 13,195
  • 10
  • 89
  • 133
  • Security is complicated, personally I would use Spring security rather than rolling my own solution because Spring Security abstracts away from many issues that I do not need to add to my code base. For example by simply using Spring Security my application will implement HSTS, HPKP, X-Frame-Options, X-XSS-Protection, CSP and so on. You may wish to add support for fragments (referred to as parts in the HttpServletRequest) in your solution. – Thomas Turrell-Croft Jul 20 '20 at 15:01
  • Apologies, I have just learnt that fragments are handled by the user agent and not sent to the server. – Thomas Turrell-Croft Jul 20 '20 at 15:09
  • I thought about this long and hard. I think the short answer is, you're right. Don't re-invent the wheel. For my simple use case, I don't need a mag wheel, just a wooden round disc. So keeping my code super simple and maintainable and implementing a 2 second solution #WorksForMe I've been running this for a few weeks now and so far so good. – sparkyspider Jul 20 '20 at 19:20