Spring Boot 将 HTTP 重定向到 HTTPS

Posted

技术标签:

【中文标题】Spring Boot 将 HTTP 重定向到 HTTPS【英文标题】:Spring Boot redirect HTTP to 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/

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

【问题讨论】:

【参考方案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 我是这样做的***.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 我已经运行了几个星期,到目前为止一切顺利。

以上是关于Spring Boot 将 HTTP 重定向到 HTTPS的主要内容,如果未能解决你的问题,请参考以下文章

负载均衡器后面带有 Spring Security 的 Spring Boot:将 HTTP 重定向到 HTTPS

Elastic Beanstalk 和 Spring Boot 将 HTTP 重定向到 HTTPS

登录后 Spring Boot 重定向到请求的 URL

(spring-boot) http 到 https 重定向,405 方法不允许消息

在 Spring Boot 应用程序中为 AWS Beanstalk 的 nginx 设置 HTTP 到 HTTPS 重定向

Spring Boot 2.3.2 server.forward-headers-strategy=framework 重定向到方案 http 而不是 https