Arseniy Ulakaiev
Arseniy Ulakaiev

Reputation: 555

Spring Boot redirect HTTP to HTTPS

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?

Upvotes: 50

Views: 120186

Answers (9)

Andy Wilkinson
Andy Wilkinson

Reputation: 116031

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.

Upvotes: 40

sparkyspider
sparkyspider

Reputation: 13509

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.

Upvotes: 1

Vlad Troyan
Vlad Troyan

Reputation: 157

@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.

Upvotes: 6

Arvind Pant
Arvind Pant

Reputation: 461

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.

Upvotes: 20

Mahozad
Mahozad

Reputation: 24472

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

Upvotes: 5

rogue lad
rogue lad

Reputation: 2452

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

Upvotes: 16

Alex Burdusel
Alex Burdusel

Reputation: 3165

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

Upvotes: 14

Rodrigo Quesada
Rodrigo Quesada

Reputation: 1470

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

Upvotes: 34

jebeaudet
jebeaudet

Reputation: 1603

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.

Upvotes: 3

Related Questions