部署在Tomcat 11上的Spring Boot应用合法端点返回404问题排查
问题背景
将基于Java 21的Spring Boot 3.3.0应用部署到Tomcat 11(监听8082端口)后,访问合法端点http://SERVERIP:8082/api/v1/auth/login始终返回404。已确认:
netstat -aon显示8082端口正常监听- catalina.out日志提示WAR包部署成功
- 已尝试在Tomcat Connector中添加
address="0.0.0.0"、关闭FirewallD,问题仍未解决
环境与配置信息
1. application.properties配置
cors.allowed-origins=http://localhost:3000 cors.allowed-headers=* cors.allow-credentials=true cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS spring.application.name=ivr-test-automation-backend server.port=8082 spring.datasource.url=jdbc:postgresql://POSTGREIP:5432/ivr_test spring.datasource.username=test_app spring.datasource.password=password spring.datasource.hikari.schema=ivr_test_automation spring.datasource.driver-class-name=org.postgresql.Driver spring.sql.init.mode=always spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl logging.level.org.springframework.web.reactive.function.client=WARN logging.level.org.springframework=WARN logging.level.root=WARN logging.level.org.hibernate=WARN logging.file.name=logs/ivr-test-automation-backend.log spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.properties.hibernate.format_sql=true # JWT remote.auth.url=http://LDAPIP:992/api/v1/authenticate/login remote.auth.secret=IVRLdap-Password #jwt.secret=ivrTestAutomationSecret jwt.expiration=36000000 jwt.secret = ${jwt.secret} spring.devtools.restart.enabled=true spring.devtools.livereload.enabled=true
2. AuthController核心代码
@RestController @RequestMapping("/api/v1/auth") public class AuthController { // 其他代码省略 @PostMapping("/login") public ResponseEntity<RestResponse<JwtAuthenticationResponse>> createAuthenticationToken(@RequestBody LoginRequest authRequest) throws Exception { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.ldapName(), authRequest.password()) ); final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.ldapName()); final String jwt = jwtUtil.generateToken(userDetails); JwtAuthenticationResponse jwtAuthenticationResponse = getJwtAuthenticationResponse(authRequest.ldapName(), jwt); return ResponseEntity.ok(RestResponse.of(jwtAuthenticationResponse, AuthMessage.SUCCESSFUL_LOGIN)); } // 其他代码省略 }
3. SecurityFilterChain配置
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenRefreshFilter jwtTokenRefreshFilter) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/v1/auth/**").permitAll() .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll() .requestMatchers("/api/v1/role/**").authenticated() .requestMatchers("/api/v1/user/**").authenticated() .requestMatchers("/api/v1/permission/**").authenticated() .requestMatchers("/api/v1/blacklist/**").authenticated() .requestMatchers("/api/v1/scenario-configuration/**").authenticated() .requestMatchers("/api/v1/scenario-configuration-history/**").authenticated() .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authenticationProvider(customAuthProvider()) .addFilterBefore(jwtTokenRefreshFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .logout(logout -> logout .logoutUrl("/api/v1/auth/logout") .logoutSuccessHandler((request, response, authentication) -> { response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write(AuthMessage.SUCCESSFUL_LOGOUT.getMessage()); })); return http.build(); }
4. Tomcat server.xml的Connector配置
<Connector port="8082" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
可能的原因与修复方案
1. 上下文路径不匹配
Tomcat部署WAR包时,默认会将WAR包名称作为上下文路径(例如WAR包名为ivr-test-automation-backend.war,则上下文路径为/ivr-test-automation-backend),此时正确请求地址应为:http://SERVERIP:8082/ivr-test-automation-backend/api/v1/auth/login
修复方式:
- 直接修改请求地址,添加上下文路径
- 将WAR包重命名为
ROOT.war,设为Tomcat默认ROOT应用 - 在Tomcat
conf/server.xml的<Host>标签内添加:<Context path="" docBase="ivr-test-automation-backend" reloadable="true"/> - 在Spring Boot的
application.properties中配置:
(需重新打包WAR包)server.servlet.context-path=/
2. WAR包部署配置缺失
Spring Boot默认打包为JAR,部署到外部Tomcat需满足以下配置:
- pom.xml中指定打包类型为WAR:
<packaging>war</packaging> - 排除内置Tomcat依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!-- 添加Jakarta Servlet API依赖(Spring Boot 3.x使用Jakarta EE规范) --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency> - 启动类继承
SpringBootServletInitializer:
未继承该类会导致外部Tomcat无法加载Spring应用上下文,所有端点无法映射。@SpringBootApplication public class IvrTestAutomationBackendApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(IvrTestAutomationBackendApplication.class); } public static void main(String[] args) { SpringApplication.run(IvrTestAutomationBackendApplication.class, args); } }
3. JWT配置错误导致应用启动异常
查看catalina.out完整日志,检查是否有启动异常:原配置中jwt.secret被设置为${jwt.secret}但未实际赋值(注释了jwt.secret=ivrTestAutomationSecret),会导致上下文初始化失败,端点无法注册。
修复:取消注释jwt.secret=ivrTestAutomationSecret,或通过环境变量传入该值。
4. 端点映射未注册
开启Spring MVC映射日志排查:
在application.properties中添加:
logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping=DEBUG
重启后查看日志,确认/api/v1/auth/login是否被注册。若未注册:
- 检查
@SpringBootApplication是否覆盖了控制器所在包,若控制器在其他包,添加@ComponentScan指定路径:@SpringBootApplication(scanBasePackages = {"com.yourpackage.controller", "com.yourpackage.security"})
5. Tomcat与Spring Boot兼容性问题
Tomcat 11基于Servlet 6.0规范,Spring Boot 3.3.0默认支持,但需确保依赖使用Jakarta EE API(而非旧的Java EE),即依赖为jakarta.servlet-api而非javax.servlet-api。
内容的提问来源于stack exchange,提问作者Mustafa

