Issue
I use a fairly standard method of redirecting Spring Boot's embedded Tomcat from HTTP to HTTPS, which repeates in a number of tutorials. The method works perfectly for the ports HTTP 8080 and HTTPS 8443, which also repeat as examples in these tutorial. However, changing numbers of these ports to less used values produces a number of problems, as described further.
The method from tutorials is as follows. Application properties:
server.http.port=8080
server.http.interface=0.0.0.0
server.port: 8443
server.ssl.enabled: true
server.ssl.key-store: classpath:selfsigned.jks
server.ssl.key-store-password: password
server.ssl.key-store-type: JKS
server.ssl.key-alias: selfsigned
Then the configuration of the additional HTTP port:
@Component
public class HttpServer {
@Value("${server.port}") int HTTPS_PORT;
@Bean
public ServletWebServerFactory servletContainer(@Value("${server.http.port}") int httpPort) {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setPort(httpPort);
connector.setRedirectPort(HTTPS_PORT);
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
}
};
tomcat.addAdditionalTomcatConnectors(connector);
return tomcat;
}
}
and finally, the configuration of the security:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception{
http.cors().and().csrf().disable();
http.requiresChannel().anyRequest().requiresSecure();
http.headers().frameOptions().sameOrigin();
http.portMapper()
.http(Integer.parseInt(Prop.get("server.http.port")))
.mapsTo(Integer.parseInt(Prop.get("server.port")));
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
The only difference from the tutorials is that I disable scanning of Jar manifest files in TomcatServletWebServerFactory
because otherwise there are problems if tomcat-embed-jasper
is included (see here).
This:
server.http.port=8080
server.port: 8443
as I said, works perfectly as in the tutorials. This:
server.http.port=8080
server.port: 5001
redirects still to HTTPS 8443, but there is nothing at 8443, since Tomcat listens at 8080 and 5001:
INFO 7360 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 5001 (https) 8080 (http) with context path ''
I scanned for 8443
every file in my app, including sources and binaries. There is no single instance of 8443
there. I also used the debugger to verify if the values are correct in setPort()
, setRedirectPort()
and portMapper()
.
This:
server.http.port=5000
server.port: 5001
produces:
0:0:0:0:0:0:0:1 - - [28/Jan/2021:18:44:46 +0100] "GET /a HTTP/1.1" 302 -
0:0:0:0:0:0:0:1 - - [28/Jan/2021:18:44:46 +0100] "GET /a HTTP/1.1" 302 -
0:0:0:0:0:0:0:1 - - [28/Jan/2021:18:44:46 +0100] "GET /a HTTP/1.1" 302 -
0:0:0:0:0:0:0:1 - - [28/Jan/2021:18:44:46 +0100] "GET /a HTTP/1.1" 302 -
...
until the browser complains about too many redirections.
The site is tested on localhost
, with an invalid certificate, thus Chrome asks for a security exception. Still, Chrome puts the site into HTTP Strict Transport Security, as can be verified at chrome://net-internals/#hsts
. Can it be, Spring Boot sends something in the HSTS headers which causes the redirection to 8443?
All in all, it seems as if 8080
and 8443
were somewhat special and declared somewhere, possibly in Tomcat or Spring Boot. How to get rid of these defaults? Any way of debugging e.g. the reason behind the redirections? Increase the verbosity of Tomcat, Spring Boot logs?
Update 1 To disable HSTS, I modified the security code:
http.headers().frameOptions().sameOrigin()
.httpStrictTransportSecurity().disable();
and removed localhost
from Chrome' HSTS, but it did not help.
Update 2 The problem persists when the application is run on an external server with a valid certificate.
Project dependencies:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.projectreactor</groupId>
<artifactId>reactor-spring</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
Solution
It's not make sense spring team insist to keep use 8443 no matter which port you config. Details see https://github.com/spring-projects/spring-security/issues/8140 https://github.com/spring-projects/spring-boot/issues/6140
Answered By - Eric Liu