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

升级至Spring Boot 3后Swagger UI无法访问问题求助

问题描述

原本运行的基于Spring Boot 2.7.6与Spring Security 5.7.5的应用,认证功能正常,可按角色访问页面,Swagger UI也能正常使用且无弃用警告。迁移至Spring Boot 3.0.0(对应Spring Security 6.0.0)后,服务器可正常启动,但所有URL均返回401状态码。

请求GET http://localhost:8080/swagger-ui/index.html时,客户端收到401响应,服务端日志显示404:

DEBUG o.s.web.servlet.handler.SimpleUrlHandlerMapping    : Mapped to ResourceHttpRequestHandler [classpath [static/]]
DEBUG o.s.web.servlet.handler.SimpleUrlHandlerMapping    : Mapped to ResourceHttpRequestHandler [classpath [static/]]
DEBUG o.s.web.servlet.handler.SimpleUrlHandlerMapping    : Mapped to ResourceHttpRequestHandler [classpath [static/]]
DEBUG o.s.web.servlet.handler.SimpleUrlHandlerMapping    : Mapped to ResourceHttpRequestHandler [classpath [static/]]
DEBUG org.springframework.web.servlet.DispatcherServlet  : GET "/swagger-ui/index.html", parameters={}
DEBUG o.s.web.servlet.handler.SimpleUrlHandlerMapping    : Mapped to ResourceHttpRequestHandler [classpath [static/]]
DEBUG o.s.w.servlet.resource.ResourceHttpRequestHandler  : Resource not found
DEBUG org.springframework.web.servlet.DispatcherServlet  : Completed 404 NOT_FOUND
DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping         : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping         : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping         : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)

相关配置文件

pom.xml

...
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.0</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    ...
    <properties>
        <java.version>17</java.version>
        <jjwt.version>0.11.5</jjwt.version>
        <springdoc.version>1.6.0</springdoc.version>
        <docx4j.version>11.3.2</docx4j.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jjwt.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>${jjwt.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
         <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-security</artifactId>
            <version>${springdoc.version}</version>
        </dependency>

        <dependency>
            <groupId>org.docx4j</groupId>
            <artifactId>docx4j-export-fo</artifactId>
            <version>${docx4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.docx4j</groupId>
            <artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
            <version>${docx4j.version}</version>
        </dependency>
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api-parent</artifactId>
            <version>3.0.1</version>
            <type>pom</type>
        </dependency>

    </dependencies>

...
</project>

SecurityConfig类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

  @Autowired
  UserDetailsService userDetailsService;

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    authenticationManagerBuilder.getDefaultUserDetailsService();
  }

  // @Override
  // public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
  // authenticationManagerBuilder.userDetailsService(this.userDetailsService)
  // .passwordEncoder(passwordEncoder());
  // }

  @Bean
  public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // @formatter:off
    http
        .headers()
            .frameOptions().disable()
            .and()
        .cors()
            .and()
        .csrf().disable()
        .exceptionHandling()
            .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)).and()
        .formLogin().disable()
        .authorizeHttpRequests(authz -> authz.requestMatchers("/api/*/auth/**").permitAll()
                                          .requestMatchers("/api/*/public/**").permitAll()
                                          .requestMatchers("/api/*/catalogs/*/documents/*/file").permitAll()
                                          .requestMatchers(req -> req.getRequestURI()
                                            .contains("swagger-ui")).permitAll()
                                          .anyRequest().authenticated());
    // @formatter:on

    return http.build();
  }

  @Bean
  public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring()
      .requestMatchers(req -> req.getRequestURI()
        .contains("mail-images"))
      .requestMatchers(req -> req.getRequestURI()
        .contains("api-docs"))
      // .requestMatchers(req -> req.getRequestURI()
      // .contains("swagger-ui"))
      .requestMatchers(req -> req.getRequestURI()
        .contains("h2-console"));
  }
}

WebMvcConfig类

import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {

  @Value("#{'${cors.allowedOrigins}'.split(',')}")
  private List<String> allowedOrigins;

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
      .allowCredentials(true)
      .allowedHeaders("*")
      .allowedOriginPatterns("http://*", "https://*")
      .allowedOrigins(this.allowedOrigins.toArray(String[]::new))
      .allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.PUT.name(), HttpMethod.PATCH.name(), HttpMethod.DELETE.name(), HttpMethod.OPTIONS.name());
  }

  private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"classpath:/static/"};

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/**")
      .addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
  }

}
排查方向
  • springdoc版本兼容问题:当前使用的springdoc.version=1.6.0仅支持Spring Boot 2.x,Spring Boot 3.x需要升级到springdoc-openapi 2.x版本(如2.0.2)。旧版本无法正确加载swagger-ui的静态资源,导致请求返回404,进而被Security拦截返回401。
  • Security请求匹配逻辑优化:Spring Security 6.0对requestMatchers的匹配逻辑做了调整,建议使用明确的路径匹配替换模糊的contains判断,比如将:
    .requestMatchers(req -> req.getRequestURI().contains("swagger-ui")).permitAll()
    
    替换为:
    .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
    
    同时可以在WebSecurityCustomizer中添加swagger相关路径的忽略规则,确保资源不被拦截。
  • UserDetailsService配置失效:当前configureGlobal方法未绑定自定义的userDetailsService,导致认证逻辑未使用你的用户数据源,所有请求无法通过认证返回401。修改该方法:
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(this.userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
    
  • WebMvc资源映射冲突:WebMvcConfig中addResourceHandlers将/**都映射到classpath:/static/,覆盖了springdoc的swagger-ui资源映射,导致静态文件无法找到。可以修改为仅映射自定义静态资源:
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
            .addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
    }
    
    或者直接删除该自定义配置,Spring Boot默认会处理静态资源映射。
  • SecurityContextRepository配置:Spring Security 6.0中默认的SecurityContext存储方式有变化,可在SecurityFilterChain中显式配置:
    http.securityContext(securityContext -> securityContext
        .securityContextRepository(new RequestAttributeSecurityContextRepository())
    );
    
    确保认证上下文能被正确存储和读取。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 00:50:30