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

Spring Boot 3.2.2嵌入Tomcat报403,外部Tomcat正常问题咨询

问题描述

我有一个Spring Boot 3.2.2应用,打包为WAR文件部署到外部Tomcat v10.1.18时,Postman及Angular应用均可正常访问控制器;但在IntelliJ或Eclipse中使用嵌入Tomcat v10.1.18运行时,Postman和Angular请求均返回403错误。这种反常现象是否由安全升级导致?

服务器调试日志

21:46:47.590 [http-nio-8080-exec-2] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
21:46:47.590 [http-nio-8080-exec-2] INFO  o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
21:46:47.590 [http-nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected StandardServletMultipartResolver
21:46:47.590 [http-nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected AcceptHeaderLocaleResolver
21:46:47.590 [http-nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected FixedThemeResolver
21:46:47.591 [http-nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@6fe243a
21:46:47.591 [http-nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - Detected org.springframework.web.servlet.support.SessionFlashMapManager@575c23f1
21:46:47.591 [http-nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
21:46:47.591 [http-nio-8080-exec-2] INFO  o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
21:46:47.602 [http-nio-8080-exec-2] DEBUG o.s.security.web.FilterChainProxy - Securing POST /api/v1/sayHello
21:46:47.615 [http-nio-8080-exec-2] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
21:46:47.615 [http-nio-8080-exec-2] DEBUG o.s.s.w.s.SessionManagementFilter - Request requested invalid session id F116C9F04B2809C02E15348F58666EFA
21:46:47.616 [http-nio-8080-exec-2] DEBUG o.s.s.w.a.Http403ForbiddenEntryPoint - Pre-authenticated entry point called. Rejecting access
21:46:47.623 [http-nio-8080-exec-2] DEBUG o.s.security.web.FilterChainProxy - Securing POST /error
21:46:47.623 [http-nio-8080-exec-2] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
21:46:47.623 [http-nio-8080-exec-2] DEBUG o.s.s.w.a.Http403ForbiddenEntryPoint - Pre-authenticated entry point called. Rejecting access

相关代码配置

SecurityConfiguration.java

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.csrf(AbstractHttpConfigurer::disable)
            .cors(httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(corsConfigurationSource()))
            .authorizeHttpRequests(request -> request.requestMatchers("/api/v1/**")
                    .permitAll().anyRequest().authenticated())
            .sessionManagement(manager -> manager.sessionCreationPolicy(STATELESS))
            .authenticationProvider(authenticationProvider()).addFilterBefore(
                    jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.setAllowedOrigins(List.of("http://localhost:4200"));
    corsConfiguration.setAllowedMethods(List.of("GET", "POST"));
    corsConfiguration.setAllowCredentials(true);
    corsConfiguration.setAllowedHeaders(List.of("*"));
    corsConfiguration.setMaxAge(3600L);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfiguration);
    return source;
}

Application.java

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.2</version>
        <relativePath/>
    </parent>
    <groupId>com.xpo.api</groupId>
    <artifactId>mapgs</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <name>mapgs api</name>
    <description>mapgs API</description>

    <properties>
        <start-class>com.xpo.api.Application</start-class>
    </properties>
    <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-security</artifactId>
        </dependency>   
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
分析与解决方案

从日志可以定位核心问题:

  • 请求携带了无效的JSESSIONID,而你的配置中设置了sessionCreationPolicy(STATELESS)(无状态模式)
  • Spring Security的SessionManagementFilter检测到无效session ID后,触发默认的Http403ForbiddenEntryPoint,直接返回403

外部Tomcat正常、内嵌Tomcat报错的差异,是两者对session Cookie的处理逻辑不同:外部Tomcat自动忽略了无效的JSESSIONID,而内嵌Tomcat下Spring Security的过滤器链严格执行了无状态模式的校验规则。

解决步骤

  1. 调整无效session校验逻辑
    在sessionManagement配置中添加无效session处理策略,让无状态模式下跳过无效session校验:

    import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
    
    .sessionManagement(manager -> manager
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .invalidSessionStrategy(new NullAuthenticatedSessionStrategy())
    )
    
  2. 验证JWT过滤器有效性
    日志显示请求进入了匿名上下文,说明jwtAuthenticationFilter可能未正确解析token或未生效:

    • 确认过滤器是否拦截了所有/api/v1/**路径的请求
    • 检查过滤器是否将解析后的用户信息正确设置到SecurityContextHolder中
  3. 修正WAR打包配置
    pom.xml中packaging设置为jar不符合WAR打包需求,需修改:

    <packaging>war</packaging>
    

    同时将内嵌Tomcat依赖设为provided,避免与外部Tomcat冲突:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    
  4. 清除无效Cookie
    测试时先清除Postman或浏览器中的JSESSIONID Cookie;如果是Angular应用,无状态模式下可将withCredentials设为false(无需携带Cookie时)。

内容的提问来源于stack exchange,提问作者softechie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 19:52:01