Spring Boot重定向HTTP到HTTPS

50

对于基于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/

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

9个回答

40

要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。


8
Jetty是否有类似的方法? - checketts
1
这段代码片段的适当位置是什么? - Jelle den Burger
1
如问题所示,TomcatEmbeddedServletContainerFactory 应该在配置类中作为一个 bean 暴露出来。 - Andy Wilkinson
2
POST 请求不会被重定向。 - nurgasemetey
3
@Oleksii 是的,没错。这个问题是关于Tomcat的,所以答案也是针对它的。如果你对其他嵌入式服务器感兴趣(并且没有使用Spring Security,因此这里的其他答案不适用于你),你应该提出另一个与你正在使用的服务器相关的问题。 - Andy Wilkinson
显示剩余7条评论

34

在你的application*.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,则应将所有这些属性添加到您的application*.properties文件中:

security.require-ssl=true

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

4
“security.require-ssl”属性已经被弃用:安全自动配置不再可定制。请提供您自己的WebSecurityConfigurer bean。 - lilalinux
这对我有用,但我需要考虑@lilalinux关于“已弃用”的评论。 - Andoy Abarquez
4
现在已经弃用了security.require-ssl - Stefan Falk
1
@Learner 我是这样做的 https://stackoverflow.com/a/56288331/826983 - Stefan Falk
1
@Learner 没错。不再使用它了。 - Stefan Falk
显示剩余4条评论

19
在Spring Boot中,需要以下依赖项:
步骤1-
<dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
</dependency>

第二步-只需要在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;
    }
}

就这样了。


16

只需跟随2个步骤。

1- 在pom.xml中添加Spring Security依赖项

    <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() - Jpnh
使用 security.require-ssl=true 时,出现 “安全自动配置不再可定制。提供您自己的 WebSecurityConfigurer bean。” 消息的解决方法是提供自己的 WebSecurityConfigurer bean。注意不要使用 <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId>,因为这会导致某些安全类出现“ClassNotFound”异常。 - Aleksandar
我在上述代码中遇到了以下异常 - 2019-12-04 14:10:55.264 INFO 12236 --- [nio-8080-exec-5] o.apache.coyote.http11.Http11Processor : 解析HTTP请求头时出错 注意:进一步的HTTP请求解析错误将以DEBUG级别记录。java.lang.IllegalArgumentException: 在方法名中发现无效字符。HTTP方法名必须是标记 at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:415) ~[tomcat-embed-core-9.0.27.jar:9.0.27] at - Priyanka Chaurishia
@PriyankaChaurishia 请将您的控制器HTTP方法重命名为有效名称。 - rogue lad

14

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

由于我没有使用默认的8080端口,我不得不将以下内容添加到我的Web安全配置中:

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

3
作为另一种选择,您还可以使用以下代码:@Value("${server.http.port}") private int httpPort;@Value("${server.port}") private int httpsPort; 作为字段,这样您就不必使用 parseInt 函数了,而且代码看起来会更加清晰简洁。 :) - Raid

6
@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,你可以在应用程序属性文件中配置转发标头的名称:

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

请查看Spring Boot文档获取更多信息。

1
你忘了提到的一件事是,上述内容需要声明implementation 'org.springframework.boot:spring-boot-starter-security'(或其Maven等效物)。 - anthonymonori
这个答案告诉你如何强制使用仅限于HTTPS的连接,但并没有告诉你如何重定向,对吧? - FerdTurgusen
如果安全版本大于等于6,则可以使用以下代码 - http.requiresChannel(req -> req.anyRequest().requiresSecure()) - undefined

5

自从Spring Boot 2中删除了 TomcatEmbeddedServletContainerFactory ,使用下面的代码:

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

3

对于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。

1

使用拦截器发送重定向到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());
    }
}

注意:您的生产Web服务器通常在1或2个端口上运行(80未安全,443安全),您应该知道它们是什么,因此我认为允许其他端口不会带来太大的安全风险。

安全性是很复杂的,个人而言我会使用Spring Security而不是自己编写解决方案,因为Spring Security可以抽象出许多我不需要添加到代码库中的问题。例如,仅通过使用Spring Security,我的应用程序就将实现HSTS、HPKP、X-Frame-Options、X-XSS-Protection、CSP等功能。您可能希望在您的解决方案中添加对片段(在HttpServletRequest中称为部分)的支持。 - Thomas Turrell-Croft
抱歉,我刚刚了解到片段是由用户代理处理而不是发送到服务器的。 - Thomas Turrell-Croft
我考虑了很久。我认为简短的答案是,你是对的。不要重复造轮子。对于我的简单用例来说,我不需要一个磁性轮毂,只需要一个木质圆盘。因此,保持我的代码超级简单和易于维护,并实施一个2秒钟的解决方案#WorksForMe。我已经运行了几周了,到目前为止一切顺利。 - sparkyspider

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接