【问题标题】:Spring Boot redirect HTTP to HTTPSSpring Boot 将 HTTP 重定向到 HTTPS
【发布时间】:2014-12-26 15:56:10
【问题描述】:

对于基于 Spring Boot 的应用程序,我在 application.properties 中配置了 ssl 属性,请在此处查看我的配置:

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/

不执行。谁遇到过类似的问题?

【问题讨论】:

    标签: java http spring-boot redirect https


    【解决方案1】:

    要让 Tomcat 执行重定向,您需要为它配置一个或多个安全约束。您可以通过使用TomcatEmbeddedServletContainerFactory 子类对Context 进行后处理来做到这一点。

    例如:

    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。如果您需要对重定向和不重定向的内容进行更多控制,您可以配置多个模式和多个约束。

    上述TomcatEmbeddedServletContainerFactory 子类的实例应使用@Configuration 类中的@Bean 方法定义为bean。

    【讨论】:

    • Jetty 有类似的方法吗?
    • 这段代码sn-p的合适位置是什么?
    • 如问题所示,TomcatEmbeddedServletContainerFactory 应作为配置类中的 bean 公开。
    • POST 请求不会被重定向。
    • @Oleksii 是的,没错。这个问题专门关于Tomcat,所以这就是答案所要解决的问题。如果您对不同的嵌入式服务器感兴趣(并且没有使用 Spring Security,因此此处的其他答案不适用),您应该问另一个特定于您正在使用的服务器的问题。
    【解决方案2】:

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

    然后通过添加一个实现EmbeddedServletContainerCustomizer@Configuration 类以及一个侦听非安全端口的新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。

    【讨论】:

      【解决方案3】:

      在您的应用程序*.properties 文件上设置此属性(如果您在代理后面运行,则为 HTTPS 标头设置相应的特定于 servlet 的配置)并设置 Spring Security(例如,具有 org.springframework. boot:spring-boot-starter-security 在你的类路径上)应该足够了:

      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
      

      【讨论】:

      • 也可以使用 server.use-forward-headers=true 启用 forwarded-* 标头
      • 属性 'security.require-ssl' 已弃用:安全自动配置不再可自定义。改为提供您自己的 WebSecurityConfigurer bean。
      • security.require-ssl 现已弃用。
      • @Learner 我是这样做的stackoverflow.com/a/56288331/826983
      • @Learner 没错。不用了。
      【解决方案4】:

      批准的答案对我来说还不够。

      我还必须将以下内容添加到我的网络安全配置中,因为我没有使用默认的 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();
          }
      }
      

      【讨论】:

      • 作为替代方案,您也可以使用:@Value("${server.http.port}") private int httpPort;@Value("${server.port}") private int httpsPort; 字段,那么您就不必 parseInt,并且代码 (imo) 看起来更清晰一些。 :)
      【解决方案5】:

      只需执行 2 个步骤。

      1- 在 pom.xml 中添加 spring 安全依赖

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

      【讨论】:

      • 太棒了。在 ALB 后面时效果很好。如果不使用 spring security 的任何其他功能,也可以添加http.authorizeRequests().antMatchers("/**").permitAll()
      • 这是“安全自动配置不再可定制。请提供您自己的 WebSecurityConfigurer bean”的解决方案。使用security.require-ssl=true 时的消息。谢谢! :) 注意:不要使用&lt;groupId&gt;org.springframework.security&lt;/groupId&gt; &lt;artifactId&gt;spring-security-config&lt;/artifactId&gt;,因为这会导致某些安全类出现ClassNotFound 异常。
      • 我在上面的代码中遇到了以下异常 - 2019-12-04 14:10:55.264 INFO 12236 --- [nio-8080-exec-5] o.apache.coyote.http11。 Http11Processor : Error parsing HTTP request header 注意:进一步出现的 HTTP 请求解析错误将在 DEBUG 级别记录。 java.lang.IllegalArgumentException:在方法名称中发现无效字符。 HTTP 方法名称必须是 org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:415) ~[tomcat-embed-core-9.0.27.jar:9.0.27] at 的标记
      • @PriyankaChaurishia 请将您的控制器 HTTP 方法重命名为有效名称。
      【解决方案6】:

      由于 TomcatEmbeddedServletContainerFactory has been removed 在 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);
              }
          };
      }
      

      【讨论】:

        【解决方案7】:

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

        就是这样。

        【讨论】:

          【解决方案8】:
          @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 Boot Documentation 了解更多信息。

          【讨论】:

          • 你忘了提到的一件事是上面需要声明implementation 'org.springframework.boot:spring-boot-starter-security'(或者它是Maven等价物)
          • 这个答案告诉你如何强制 https-only连接,而不是如何重定向,不是吗?
          【解决方案9】:

          使用拦截器发送重定向到 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 security 而不是滚动我自己的解决方案,因为 Spring Security 抽象出许多我不需要添加到我的代码库中的问题。例如,通过简单地使用 Spring Security,我的应用程序将实现 HSTS、HPKP、X-Frame-Options、X-XSS-Protection、CSP 等。您可能希望在您的解决方案中添加对片段(在 HttpServletRequest 中称为部分)的支持。
          • 抱歉,我刚刚了解到片段由用户代理处理,而不是发送到服务器。
          • 我想了很久。我认为简短的回答是,你是对的。不要重新发明轮子。对于我的简单用例,我不需要磁轮,只需要一个木制圆盘。因此,保持我的代码超级简单和可维护,并实现 2 秒解决方案#WorksForMe 我已经运行了几个星期,到目前为止一切顺利。
          猜你喜欢
          • 2019-09-21
          • 2019-05-15
          • 2021-03-31
          • 2021-12-12
          • 2016-11-06
          • 2014-12-13
          • 2018-08-09
          • 2015-08-19
          • 2020-10-07
          相关资源
          最近更新 更多