HTTPS mit Spring Boot

Dies ist das fünfte Kapitel der Tutorial-Beitragsreihe zu Spring Security. In diesem Beitrag geht es darum, den integrierten Tomcat von Spring Boot so zu konfigurieren, dass die Applikation nur noch mit dem HTTPS-Protokoll arbeitet und alle HTTP-Anfragen auf HTTPS umgeleitet werden. Grundsätzlich kann hierfür ein neues Spring-Boot-Projekt verwendet werden, jedoch wurde für diesen Beitrag das Projekt aus Default Schutz durch Spring Security verwendet. Den Source Code zu diesem Tutorial findet ihr auf dem Micromata Github Bereich.

Einen Überblick über das gesamte Tutorial, bereits veröffentlichte Kapitel sowie den Ausblick auf kommende Kapitel ist hier zu finden. Falls ihr Fragen zum Tutorial oder Source Code habt, meldet euch einfach über das LABS Kontaktformular oder wendet euch direkt über den Micromata Github Bereich an mich, Jürgen Fast (Micromata).

Das Zertifikat

Zunächst benötigen wir ein SSL-Zertifikat. In diesem Beitrag wird dafür ein selbstsigniertes Zertifikat benutzt. Dieses lässt sich beispielsweise folgendermaßen erstellen:

keytool -genkey -keyalg RSA -alias myselfsigned -storetype PKCS12 -keysize 2048 -keystore keystore.p12 -validity 360

Es folgt eine Anfrage nach einem Passwort für das Zertifikat und anschließend eine Reihe ausfüllbarer Felder, die jedoch alle optional sind und übersprungen werden können.

Das Zertifikat keystore.p12 wird anschließend unter /resources/ssl/keystore.p12 abgelegt.

Die Konfiguration

In der Konfiguration wird nun Folgendes eingetragen:

server:
  port: 8443
  ssl:
    key-store: src/main/resources/ssl/keystore.p12
    key-store-password: mypassword
    keyStoreType: PKCS12
    keyAlias: myselfsigned

Nun ist unsere Applikation ausschließlich über HTTPS unter dem Port 8443 erreichbar.

HTTP Redirect

Da wir jedoch wollen, dass unsere Applikation auch über HTTP erreichbar ist, damit ein Aufruf wie meineApplikation.de möglich ist, benötigen wir einen Redirect von HTTP auf HTTPS. Jedoch ermöglicht Spring Boot nur die Konfiguration eines Tomcat Connectors über die Konfigurationsdatei. Der andere Connector muss programmatisch angelegt werden. Da es einfacher ist den HTTP Connector programmatisch zu erstellen, passen wir die Klasse SimpleSpringSecurityApplication im Folgenden entsprechend an. Zunächst fügen wir zwei Membervariablen hinzu, die mit der Konfigurationsdatei application.yml verknüpft sind. Diese Verknüpfung wird über die Annotation @Value hergestellt.

@Value("${server.port}")
private int serverPort;

@Value("${server.http.port}")
private int serverHttpPort;

server.port haben wir bereits mit dem Wert 8443 konfiguriert, jedoch müssen wir nun noch server.http.port nachtragen.

server:
  port: 8443
  http.port: 8080
  ssl:
    key-store: src/main/resources/ssl/keystore.p12
    key-store-password: mypassword
    keyStoreType: PKCS12
    keyAlias: myselfsigned

Anschließend fügen wir den neuen Connector in SimpleSpringSecurityApplication hinzu.

@Bean
public EmbeddedServletContainerFactory createAdditionalTomcatConnector() {
    TomcatEmbeddedServletContainerFactory embeddedTomcat = 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);
        }
    };
    embeddedTomcat.addAdditionalTomcatConnectors(createHttpConnector());
    return embeddedTomcat;
}

private Connector createHttpConnector() {
    Connector httpConnector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    httpConnector.setScheme("http");
    httpConnector.setPort(serverHttpPort);
    httpConnector.setRedirectPort(serverPort);
    return httpConnector;
}

Um den zweiten Tomcat Connector anzulegen, benötigen wir zunächst eine EmbeddedServletContainerFactory-Bean mit TomcatEmbeddedServletContainerFactory als Implementation. Hier überschreiben wir die Methode postProcessContext, welche in der Superklasse leer ist. Diese Methode wird genutzt, um den Kontext anzupassen, bevor er den Tomcat Server erreicht. In der Methode definieren wir das User Constraint als CONFIDENTIAL und das Pattern auf /*, damit der Redirect später auf alle Anfragen angewendet wird. Würden wir das User Constraint beispielsweise auf NONE setzen, so würde keinerlei Weiterleitung stattfinden. An dieser Stelle kann man die Weiterleitung auch auf bestimmte URLs begrenzen, falls dies gewünscht ist. Anschließend wird dem integrierten Tomcat der neue HTTP Connector, welcher in der überschaubaren Methode createHttpConnector erstellt wird, hinzugefügt. Die endgültige Klasse SimpleSpringSecurityApplication sieht dann folgendermaßen aus:

package de.micromata.spring.security.example;

import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class SimpleSpringSecurityApplication {

    @Value("${server.port}")
    private int serverPort;

    @Value("${server.http.port}")
    private int serverHttpPort;

    public static void main(String[] args) {
        SpringApplication.run(SimpleSpringSecurityApplication.class, args);
    }

    @Bean
    public EmbeddedServletContainerFactory createAdditionalTomcatConnector() {
        TomcatEmbeddedServletContainerFactory embeddedTomcat = 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);
            }
        };
        embeddedTomcat.addAdditionalTomcatConnectors(createHttpConnector());
        return embeddedTomcat;
    }

    private Connector createHttpConnector() {
        Connector httpConnector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        httpConnector.setScheme("http");
        httpConnector.setPort(serverHttpPort);
        httpConnector.setRedirectPort(serverPort);
        return httpConnector;
    }
}

Autor

Jürgen Fast arbeitet seit 2014 bei der Micromata GmbH als Softwareentwickler in verschiedenen Projekten der Logistik- und Automobilbranche. Sein Schwerpunkt liegt in der Entwicklung von javabasierten Webapplikationen.