返回介绍

9.3 HTTP 通信安全

发布于 2025-04-26 13:16:46 字数 5417 浏览 0 评论 0 收藏 0

HTTP 通信安全主要从三个方面入手:

(1)使用 HTTPS 代替 HTTP。

(2)Strict-Transport-Security 配置。

(3)代理服务器配置。

其中第 2 点我们前面已经讲过,这里主要和大家分享第 1 点和第 3 点。

9.3.1 使用 HTTPS

作为一个框架,Spring Security 不处理 HTTP 连接问题,因此不直接提供对 HTTPS 的支持。但是,它提供了许多有助于 HTTPS 使用的功能。

接下来我们通过一个简单的案例来演示其具体用法。

首先创建一个 Spring Boot 项目,引入 Spring Security 和 Web,然后参考 9.2.3 小节中的方式创建 HTTPS 证书,并配置到 Spring Boot 项目中。

配置完成后,我们再在 application.properties 中添加如下配置修改项目端口号:

    spring.security.user.name=javaboy
    spring.security.user.password=123
    server.port=8443

此时我们的项目就支持 HTTPS 访问了,HTTPS 的访问端口是 8443。为了更好地演示 Spring Security 的功能,我们需要项目同时支持 HTTPS 和 HTTP,所以还需要在项目中添加如下配置:

    @Configuration
    public class TomcatConfig {
       @Bean
       TomcatServletWebServerFactory tomcatServletWebServerFactory() {
           TomcatServletWebServerFactory factory =
                                             new TomcatServletWebServerFactory();
           factory.addAdditionalTomcatConnectors(createTomcatConnector());
           return factory;
       }
       private Connector createTomcatConnector() {
           Connector connector = new
                   Connector("org.apache.coyote.http11.Http11NioProtocol");
           connector.setScheme("http");
           connector.setPort(8080);
           return connector;
       }
    }

这相当于又添加了一个 Connector,让 Tomcat 同时监听 8080 端口。

配置完成后,启动项目,控制台可以看到如下日志,表示项目现在同时支持 HTTP 和 HTTPS 了:

    Tomcat started on port(s): 8443 (https) 8080 (http) with context path ''

接下来,我们创建两个测试接口,代码如下:

    @RestController
    public class HelloController {
       @GetMapping("/https")
       public String https() {
           return "https";
       }
       @GetMapping("/http")
       public String http() {
           return "http";
       }
    }

/http 接口表示可以直接使用 HTTP 协议访问,/https 接口表示只可以通过 HTTPS 协议访问。我们来看一下 SecurityConfig 中的配置:

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http.authorizeRequests()
                   .anyRequest().authenticated()
                   .and()
                   .formLogin()
          .and()
          .requiresChannel()
          .antMatchers("/https").requiresSecure()
          .antMatchers("/http").requiresInsecure()
          .and()
          .csrf().disable();
       }
    }

通过 requiresChannel() 方法开启配置,requiresSecure() 表示该请求是 HTTPS 协议,如果不是,则重定向到 HTTPS 协议请求;requiresInsecure() 则要求请求是 HTTP 协议,如果不是,则重定向到 HTTP 协议请求。没有列举出来的请求,则是两种协议都可以。

配置完成后,重启项目。使用如下两个地址都可以访问到登录页面:

- http://localhost:8080/login

- https://localhost:8443/login

现在我们使用 http://localhost:8080/login 地址登录成功后,访问/http 没问题,访问/https 则会自动重定向到 https://localhost:8443/https。之所以重定向到 8443 端口,并非因为我们项目端口是 8443,而是因为 8443 是 HTTPS 的默认监听端口,无论项目端口号是多少,这里都会重定向到 8443 端口。

现在修改 application.properties 中的配置,将 server.port 改为 8444,即将项目中 HTTPS 的监听端口改为 8444,那么如何让这里重定向到 8444 呢?配置如下:

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
       @Override
       protected void configure(HttpSecurity http) throws Exception {
           http.authorizeRequests()
                   .anyRequest().authenticated()
                   .and()
                   .formLogin()
                   .and()
                   .portMapper()
                   .http(8080).mapsTo(8444)
                   .and()
                   .requiresChannel()
                   .antMatchers("/https").requiresSecure()
                   .antMatchers("/http").requiresInsecure()
                   .and()
                   .csrf().disable();
       }
    }

通过 portMapper() 方法开启端口的映射配置,这里我们配置将 HTTP 端口 8080 转发到 HTTPS 端口 8444。配置完成后,当用户再次访问 http://localhost:8080/https 时,就会自动重定向到 https://localhost:8444/https 地址。

提示

在测试时有一个问题需要注意。如果一开始使用 HTTP 协议登录,则登录成功访问/http、/https 都没有问题,都会自动进行重定向,但是如果一开始使用了 HTTPS 协议登录,则在登录成功后,从 HTTPS 协议重定向到 HTTP 协议时,会让用户重新登录。出现这一问题的原因在于,如果用户使用 HTTPS 协议登录,则返回的 Cookie 中包含了 Secure 标记(见图 9-14),该属性表示该 Cookie 只可以在安全环境下传输(即 HTTPS 协议中传输),当从 HTTPS 重定向到 HTTP 时,HTTP 请求并不会自动携带该 Cookie,所以就会让用户重新登录。反之,如果一开始登录使用了 HTTP 协议,则返回的 Cookie 中没有 Secure 标记,该 Cookie 在 HTTPS 和 HTTP 环境下都可以传输,因此可以无缝重定向。同时,由于我们的两个测试地址域名都是 localhost,而 Cookie 是不区分端口号的,如果 Cookie 名相同,会自动覆盖,并且读取的是相同的数据。所以,当从 HTTPS 协议重定向到 HTTP 协议时,浏览器上 HTTPS 的 JSESSIONID 还在,但是 HTTP 协议又用不了该 Cookie,就会导致 HTTP 协议一直登录失败,此时只要清除浏览器缓存即可。

图 9-14 HTTPS 环境下,Cookie 中含有 Secure 标记

这就是 Spring Security 中关于 HTTP 请求转发到 HTTPS 的配置,当然,HTTP 自动转发到 HTTPS 也可以在 Servlet 容器层面进行配置,这个不属于本书的范畴,这里不做过多介绍,感兴趣的读者可以自行查找资料学习。

9.3.2 代理服务器配置

在分布式环境下或者集群环境下,项目中可能会引入 Nginx 作为负载均衡服务器,这个时候需要确保自己的代理服务器和 Spring Security 的配置是正确的,以便 Spring Security 能够准确获取请求的真实 IP,避免各种潜在的威胁或应用程序错误。

开发者需要在代理服务器中配置 X-Forwarded-*以便将客户端信息转发到真实的后端,后端收到请求后,不同的 Servlet 容器以及 Spring 框架都有各自的方式从请求头中获取客户端真实的 IP 地址、Host 以及请求协议等,并重新设置在相应的 request.getXXX 方法上,开发者调用这些方法就可以成功获取到客户端的真实信息,并不会感觉到有代理服务器存在。例如,在 Tomcat 中是通过 RemoteIpValve 类来处理,在 Jetty 中是通过 ForwardedRequestCustomizer 来处理,Spring 框架则是通过 ForwardedHeaderFilter 过滤器来处理。实际项目中,选择一种方式即可。

由于这里并不涉及 Spring Security 的相关知识,因此不做过多介绍,读者在实际项目开发中注意此问题即可。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。