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();
}
}
И это в основном это