Spring Boot+Spring Security启用HTTPS后无法暴露HTTP端点的问题
问题描述
我的Spring Boot应用集成了Spring Security,配置了自定义登录页与验证系统,整体运行在HTTPS环境中。为避免给Prometheus配置TLS支持,我想暴露某个HTTP端点供它采集指标。
我尝试了几种配置HTTP/HTTPS双端口的方案,但访问http://localhost:8081/greeting时出现解析HTTP请求头的错误,而https://localhost:8443/greeting可以正常访问。修改Security配置类后问题仍未解决,还出现了从8443到8080的重定向,求解决方案。
错误堆栈信息
2022-10-07 17:21:01.190 INFO 14776 --- [nio-8081-exec-2] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException: Invalid character found in method name [0x160x030x010x020x000x010x000x010xfc0x030x030xedv0x87l0xf9G0xb80xf10xae}0xd00x130x1e0xe10x0az0x810xc50xee0xd220xb10xf10xb2O%k0x92Ipd0x95 ]. HTTP method names must be tokens at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:419) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:271) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.63.jar:9.0.63] at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
现有配置代码
Security配置类
package com.andrekreou.iot.authentication.security; import com.andrekreou.iot.authentication.user.ApplicationUserService; import lombok.AllArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @AllArgsConstructor @EnableWebSecurity public class ApplicationSecurityConfig { private final ApplicationUserService applicationUserService; private final BCryptPasswordEncoder bCryptPasswordEncoder; @Bean protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .requiresChannel() .antMatchers("/greeting") .requiresInsecure() .and() .authorizeRequests() .antMatchers("/api/v*/registration/**","/register*","/login","/registration","/registration-complete","/greeting").permitAll() .anyRequest() .authenticated() .and() .formLogin() .loginPage("/login") .usernameParameter("email") .permitAll() .defaultSuccessUrl("/",true) .and() .logout() .logoutUrl("/logout") .clearAuthentication(true) .invalidateHttpSession(true) .deleteCookies("JSESSIONID","Idea-2e8e7cee") .logoutSuccessUrl("/login"); return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public DaoAuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setPasswordEncoder(bCryptPasswordEncoder); provider.setUserDetailsService(applicationUserService); return provider; } }
主类
package com.andrekreou.iot; import io.micrometer.core.aop.TimedAspect; import io.micrometer.core.instrument.MeterRegistry; import org.apache.catalina.connector.Connector; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication @EnableJpaRepositories public class IotApplication { public static void main(String[] args) { SpringApplication.run(IotApplication.class, args); } @Bean public TimedAspect timedAspect(MeterRegistry registry) { return new TimedAspect(registry); } @Bean public ServletWebServerFactory servletContainer() { TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); tomcat.addAdditionalTomcatConnectors(createStandardConnector()); return tomcat; } private Connector createStandardConnector() { Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); connector.setPort(httpPort); return connector; } @Value("${server.http.port}") private int httpPort; }
配置文件(application.properties)
#Server properties for HTTPS configuration server.ssl.enabled=true server.ssl.key-store-type=PKCS12 server.ssl.key-store=classpath:local-ssl.p12 server.ssl.key-store-password=Puredrummer1 server.ssl.key-password=Puredrummer1 server.servlet.context-path=/ server.ssl.key-alias=local_ssl server.port=8443 server.http.port=8081
测试控制器
package com.andrekreou.iot.control.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class Sample { @GetMapping(value = "/greeting") public String greeting() { return "I am working with both HTTP and HTTPS"; } }
更新说明
修改Security配置类后,错误仍未解决,且出现从8443到8080的重定向。
解决方案
1. 错误根源分析
请求解析错误是因为用HTTPS协议访问了HTTP端口8081——请求里的0x16是TLS握手标志位,Tomcat的HTTP连接器收到HTTPS请求自然解析失败。
重定向到8080的问题,是Spring Security的requiresChannel规则与Tomcat连接器配置不匹配导致的。
2. 修复步骤
步骤1:修正Tomcat连接器配置
确保HTTP连接器明确为HTTP模式,不继承主端口的SSL配置:
private Connector createStandardConnector() { Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); // 明确标记为HTTP协议 connector.setScheme("http"); connector.setSecure(false); connector.setPort(httpPort); // 设置重定向到HTTPS端口,避免地址错误 connector.setRedirectPort(8443); return connector; }
步骤2:调整Spring Security通道规则
根据需求选择以下两种配置之一:
- 需求1:仅/greeting允许HTTP,其余强制HTTPS
@Bean protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .requiresChannel(channel -> channel .antMatchers("/greeting").requiresInsecure() .anyRequest().requiresSecure() ) .authorizeRequests() .antMatchers("/api/v*/registration/**","/register*","/login","/registration","/registration-complete","/greeting").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .usernameParameter("email") .permitAll() .defaultSuccessUrl("/",true) .and() .logout() .logoutUrl("/logout") .clearAuthentication(true) .invalidateHttpSession(true) .deleteCookies("JSESSIONID","Idea-2e8e7cee") .logoutSuccessUrl("/login"); return http.build(); }
- 需求2:/greeting同时支持HTTP和HTTPS,其余强制HTTPS
移除requiresInsecure()规则,改为仅对非/greeting的请求强制HTTPS:
.requiresChannel(channel -> channel .requestMatchers(r -> !"/greeting".equals(r.getRequestURI())) .requiresSecure() )
步骤3:验证配置
启动应用后:
- 访问
http://localhost:8081/greeting,应正常返回内容; - 访问
https://localhost:8443/greeting,根据配置要么正常返回,要么重定向到HTTP; - 访问登录页等其他页面,确认仍强制使用HTTPS。
内容的提问来源于stack exchange,提问作者Alex_Pap
相关产品推荐
相关产品推荐

