After upgrade to Spring Boot 3 Swagger UI is not accessable

2.8k views Asked by At

I had a Spring Boot 2.7.6 app with Spring Security 5.7.5 running without problems: authentication works, I navigate through the different pages according to roles, Swagger UI is usable, etc. I don't have a deprecated warning for your information.

I migrated to Spring Boot 3.0.0 and therefore Spring Security 6.0.0. I followed the migration guides and the server starts without error. But my URLs no longer work. I have a 401 status.

Issue #1: GET http://localhost:8080/swagger-ui/index.html results in a 401 on client side.

In the logs I have a 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 class

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 class

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);
  }
 
}

I tried some differents configuration according with

Do you have a search lead because I'm drying up after several attempts?

1

There are 1 answers

2
Nicolas On BEST ANSWER

I found the solution : with spring boot 3 it seems to use springdoc-openapi-starter-webmvc-ui (https://springdoc.org/v2/). I have added the following dependency and now i can use swagger

   <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.0.2</version>
    </dependency>

The dependency springdoc-openapi-ui is not applicable to spring boot 3. I remo