You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

部署在Tomcat 11上的Spring Boot应用合法端点返回404问题排查

Spring Boot 3.3.0部署到Tomcat 11后端点返回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应用
  • 在Tomcatconf/server.xml的<Host>标签内添加:
    <Context path="" docBase="ivr-test-automation-backend" reloadable="true"/>
    
  • 在Spring Boot的application.properties中配置:
    server.servlet.context-path=/
    
    (需重新打包WAR包)

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:
    @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);
        }
    }
    
    未继承该类会导致外部Tomcat无法加载Spring应用上下文,所有端点无法映射。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 20:24:54