Spring Boot перенаправить HTTP на HTTPS

Для приложения на основе Spring Boot я настроил свойства ssl в application.properties, см. Мою конфигурацию здесь:

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

И я добавил соединение в Application.class, как

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

Но когда я пытаюсь следующее по

http://127.0.0.1:9090/

перенаправить на

https://127.0.0.1:8443/

не выполняется. Кто сталкивался с подобной проблемой?

7 ответов

Решение

Чтобы Tomcat выполнил перенаправление, вам необходимо настроить его с одним или несколькими ограничениями безопасности. Вы можете сделать это путем постобработки Context используя TomcatEmbeddedServletContainerFactory подкласс.

Например:

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

Из-за CONFIDENTIAL а также /*, это заставит Tomcat перенаправить каждый запрос на HTTPS. Вы можете настроить несколько шаблонов и несколько ограничений, если вам нужен больший контроль над тем, что перенаправлено, а что нет.

Установка этого свойства в файле приложения *.properties (и соответствующая конфигурация для сервлетов для заголовков HTTPS в случае, если вы работаете за прокси-сервером) и настройка Spring Security (например, наличие org.springframework.boot:spring-boot- Стартера-безопасности на вашем classpath) должно быть достаточно:

security.require-ssl=true

Теперь по какой-то причине эта конфигурация не учитывается, когда базовая аутентификация отключена (по крайней мере, в старых версиях Spring Boot). Так что в этом случае вам нужно будет сделать дополнительный шаг и выполнить его самостоятельно, вручную настроив безопасность своего кода, например:

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

Итак, если вы используете Tomcat за прокси-сервером, у вас будут все эти свойства в файле *.properties вашего приложения:

security.require-ssl=true

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

В Spring-Boot нужна зависимость ниже

Шаг 1-

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

Шаг 2- Просто нужно сделать следующие настройки в файле application.properties

 - 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

Шаг 3- теперь необходимо сгенерировать сертификат, используя вышеуказанные детали.

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

Шаг 4- переместите сертификат в папку ресурсов в вашей программе.

Шаг 5 - Создать класс конфигурации

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

}

вот и все.

Выполните только 2 шага.

1- Добавить зависимость безопасности весны в pom.xml

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

2- Добавьте этот класс в корневой пакет вашего приложения.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requiresChannel().anyRequest().requiresSecure();
    }
}

Одобренного ответа мне было недостаточно.

Мне также пришлось добавить следующее в мою конфигурацию веб-безопасности, так как я не использую порт 8080 по умолчанию:

@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();
    }
}
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
        
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    
    http.requiresChannel().anyRequest().requiresSecure();   
  }
}

Если ваше приложение находится за балансировщиком нагрузки или обратным прокси-сервером, вам нужно будет добавить в файл application.properties следующее:

server.forward-headers-strategy=NATIVE

Это предотвратит цикл перенаправления.

Если вы используете Tomcat, вы можете настроить имена прямых заголовков в своем файле application.properties:

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

См. Документацию по загрузке Spring для получения дополнительной информации.

Поскольку TomcatEmbeddedServletContainerFactory был удален в Spring Boot 2, используйте это:

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

Для Jetty (протестировано с 9.2.14), вам нужно добавить дополнительную конфигурацию в WebAppContext (настроить pathSpec на ваш вкус):

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

Затем подключите этот класс, добавив @Configuration реализация класса EmbeddedServletContainerCustomizer вместе с новым Connector которые слушают незащищенный порт:

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

Это подразумевает, что SSL Connector в этом примере уже настроен и прослушивает порт 443.

Использование перехватчика для отправки перенаправления на https://

(не требует Spring Security)

Все это кажется сложным. Почему бы нам просто не добавить перехватчик, который проверяет порт, и, если это порт 80, перенаправить его на тот же URL-адрес, но с префиксом https:// вместо этого.

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

}

и не забудьте зарегистрировать свой милый перехватчик

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

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

Примечание. Ваш производственный веб-сервер обычно работает на 1 или 2 портах (80 незащищенных, 443 безопасных), и вы должны знать, что они собой представляют, поэтому я не вижу в этом большой угрозы безопасности, допускающей использование других портов.

Для Spring Boot 2 я настроил свой сервер ресурсов со следующим @Configuration:

@Configuration
@EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .requiresChannel()
                /* Require HTTPS evereywhere*/
                .antMatchers("/**")
                    .requiresSecure();
    }    
}

И это в основном это

Другие вопросы по тегам