Java HSTS案例

wen java案例 2

本文目录导读:

Java HSTS案例

  1. 什么是HSTS
  2. 实现方式
  3. 最佳实践
  4. 注意事项

我来为您详细介绍Java中HSTS(HTTP Strict Transport Security)的实现案例。

什么是HSTS

HSTS是一种安全机制,强制浏览器只能通过HTTPS访问网站,防止中间人攻击和SSL剥离攻击。

实现方式

在Servlet Filter中实现

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HSTSFilter implements Filter {
    // HSTS配置参数
    private static final long MAX_AGE = 31536000L; // 1年
    private static final boolean INCLUDE_SUBDOMAINS = true;
    private static final boolean PRELOAD = false;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化配置
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        // 只在HTTPS请求时添加HSTS头
        if (httpRequest.isSecure()) {
            StringBuilder hstsValue = new StringBuilder();
            hstsValue.append("max-age=").append(MAX_AGE);
            if (INCLUDE_SUBDOMAINS) {
                hstsValue.append("; includeSubDomains");
            }
            if (PRELOAD) {
                hstsValue.append("; preload");
            }
            httpResponse.setHeader("Strict-Transport-Security", hstsValue.toString());
        }
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
        // 清理资源
    }
}

Spring Boot配置方式

使用配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .requiresChannel()
                .anyRequest().requiresSecure()  // 强制HTTPS
                .and()
            .headers()
                .httpStrictTransportSecurity()
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000)
                    .preload(false);
    }
}

使用Filter注册

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HSTSConfiguration {
    @Bean
    public FilterRegistrationBean<HSTSFilter> hstsFilter() {
        FilterRegistrationBean<HSTSFilter> registrationBean = 
            new FilterRegistrationBean<>();
        registrationBean.setFilter(new HSTSFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

完整的HSTS过滤器实现

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.logging.Logger;
@WebFilter("/*")
public class CompleteHSTSFilter implements Filter {
    private static final Logger LOGGER = 
        Logger.getLogger(CompleteHSTSFilter.class.getName());
    private HSTSConfig config;
    public static class HSTSConfig {
        private long maxAge = 31536000; // 1年
        private boolean includeSubDomains = true;
        private boolean preload = false;
        private String[] excludedPaths = new String[]{};
        // Getters and setters
        public long getMaxAge() { return maxAge; }
        public void setMaxAge(long maxAge) { this.maxAge = maxAge; }
        public boolean isIncludeSubDomains() { return includeSubDomains; }
        public void setIncludeSubDomains(boolean includeSubDomains) { 
            this.includeSubDomains = includeSubDomains; 
        }
        public boolean isPreload() { return preload; }
        public void setPreload(boolean preload) { this.preload = preload; }
        public String[] getExcludedPaths() { return excludedPaths; }
        public void setExcludedPaths(String[] excludedPaths) { 
            this.excludedPaths = excludedPaths; 
        }
    }
    @Override
    public void init(FilterConfig filterConfig) {
        this.config = new HSTSConfig();
        // 从web.xml或配置读取参数
        String maxAge = filterConfig.getInitParameter("maxAge");
        if (maxAge != null) {
            config.setMaxAge(Long.parseLong(maxAge));
        }
        String includeSubDomains = filterConfig.getInitParameter("includeSubDomains");
        if (includeSubDomains != null) {
            config.setIncludeSubDomains(Boolean.parseBoolean(includeSubDomains));
        }
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String requestURI = httpRequest.getRequestURI();
        // 检查是否在排除路径中
        if (!isExcludedPath(requestURI)) {
            // 只在HTTPS请求时添加HSTS头
            if (httpRequest.isSecure()) {
                String hstsValue = buildHSTSHeader();
                httpResponse.setHeader("Strict-Transport-Security", hstsValue);
                LOGGER.fine("Added HSTS header to: " + requestURI);
            } else {
                // 可选:重定向HTTP到HTTPS
                String httpsURL = "https://" + httpRequest.getServerName() 
                                + httpRequest.getRequestURI();
                httpResponse.sendRedirect(httpsURL);
                return;
            }
        }
        chain.doFilter(request, response);
    }
    private String buildHSTSHeader() {
        StringBuilder hstsValue = new StringBuilder();
        hstsValue.append("max-age=").append(config.getMaxAge());
        if (config.isIncludeSubDomains()) {
            hstsValue.append("; includeSubDomains");
        }
        if (config.isPreload()) {
            hstsValue.append("; preload");
        }
        return hstsValue.toString();
    }
    private boolean isExcludedPath(String path) {
        for (String excludedPath : config.getExcludedPaths()) {
            if (path.startsWith(excludedPath)) {
                return true;
            }
        }
        return false;
    }
    @Override
    public void destroy() {
        // 清理资源
    }
}

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!-- HSTS Filter -->
    <filter>
        <filter-name>hstsFilter</filter-name>
        <filter-class>com.example.HSTSFilter</filter-class>
        <init-param>
            <param-name>maxAge</param-name>
            <param-value>31536000</param-value>
        </init-param>
        <init-param>
            <param-name>includeSubDomains</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>preload</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>hstsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

测试用例

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class HSTSFilterTest {
    @Autowired
    private MockMvc mockMvc;
    @Test
    public void testHSTSHeaderPresent() throws Exception {
        mockMvc.perform(get("/api/test").secure(true))
               .andExpect(status().isOk())
               .andExpect(header().string("Strict-Transport-Security", 
                   "max-age=31536000; includeSubDomains"));
    }
    @Test
    public void testHSTSHeaderNotOnHTTP() throws Exception {
        mockMvc.perform(get("/api/test").secure(false))
               .andExpect(status().is3xxRedirection())
               .andExpect(header().string("Location").startsWith("https://"));
    }
}

最佳实践

配置建议

// 生产环境配置
@Configuration
public class ProductionHSTSConfig {
    @Bean
    public FilterRegistrationBean<HSTSFilter> hstsFilter() {
        FilterRegistrationBean<HSTSFilter> registrationBean = 
            new FilterRegistrationBean<>();
        CompleteHSTSFilter.HSTSConfig config = new CompleteHSTSFilter.HSTSConfig();
        config.setMaxAge(63072000); // 2年
        config.setIncludeSubDomains(true);
        config.setPreload(true);
        // 排除不需要HSTS的路径
        config.setExcludedPaths(new String[]{"/health", "/metrics"});
        registrationBean.setFilter(new CompleteHSTSFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

验证HSTS配置

# 使用curl测试HSTS头
curl -I -k https://yourdomain.com
# 期望输出
# Strict-Transport-Security: max-age=31536000; includeSubDomains

注意事项

  1. 先确保HTTPS正常工作:启用HSTS前,确保所有资源都通过HTTPS提供服务
  2. 合理设置max-age:开发环境短时间,生产环境至少1年
  3. 预加载(preload):使用前需提交到浏览器预加载列表
  4. 排除健康检查路径:避免影响监控系统

通过以上实现,您的Java应用就可以正确配置HSTS,提高Web安全性。

抱歉,评论功能暂时关闭!