Схемы исчезают из компонентов при программном добавлении схемы безопасности

Недавно я перешел с Springfox на Springdoc-openapi для создания моего OpenAPI для службы Spring Boot Rest API.

Все работало отлично, пока я не добавил схему безопасности. Как только я это сделал, мои схемы больше не отображаются, а на странице SwaggerUI появляется ошибка:

Could not resolve reference: Could not resolve pointer: /components/schemas/Ping does not exist in document

Я настраиваю свою конфигурацию программно и имею 2 группы.

Я использую Spring Boot v2.4.0 с springdoc-openapi-ui v1.5.1.

Фрагмент моего pom.xml:

        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-hateoas</artifactId>
            <version>1.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-security</artifactId>
            <version>1.5.1</version>
        </dependency>

Фрагмент из конфигурации:

    @Bean
public GroupedOpenApi apiV1() {
    String[] paths = {"/v1/**"};
    String[] packagesToScan = {"com.test.controller"};
    return GroupedOpenApi.builder()
            .group("v1")
            .packagesToScan(packagesToScan)
            .pathsToMatch(paths)
            .addOpenApiCustomiser(buildV1OpenAPI())
            .build();
}

@Bean
public GroupedOpenApi apiV2() {
    String[] paths = {"/v2/**"};
    String[] packagesToScan = {"com.test.controller"};
    return GroupedOpenApi.builder()
            .group("v2")
            .packagesToScan(packagesToScan)
            .pathsToMatch(paths)
            .addOpenApiCustomiser(buildV2OpenAPI())
            .build();
}

public OpenApiCustomiser buildV1OpenAPI() {
    return openApi -> openApi.info(apiInfo().version("v1"));
}

public OpenApiCustomiser buildV2OpenAPI() {
    final String securitySchemeName = "Access Token";
    return openApi -> openApi.info(apiInfo().version("v2"))
            .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
            .components(new Components().addSecuritySchemes(securitySchemeName, new SecurityScheme()
                    .type(SecurityScheme.Type.APIKEY)
                    .in(SecurityScheme.In.HEADER)
                    .name(HttpHeaders.AUTHORIZATION)));
}

// Describe the apis
private Info apiInfo() {
    return new Info()
            .title("Title")
            .description("API Description");
}

Для моей группы v1 все работает нормально. Мои схемы отображаются на странице пользовательского интерфейса Swagger, и я вижу их в разделе компонентов сгенерированного API-документа.

    "components": {
    "schemas": {
        "ApplicationErrorResponse": {
            ...
            }
        },
        "Ping": {
            ...
        }
    }
}

Для моей группы v2 схемы не создаются.

    "components": {
    "securitySchemes": {
        "Access Token": {
            "type": "apiKey",
            "name": "Authorization",
            "in": "header"
        }
    }
}

Есть идеи, почему мои схемы не сканируются и не добавляются автоматически при программном добавлении схемы безопасности к компонентам OpenAPI? Я что-то упустил в своей конфигурации?

Вот мое сопоставление запросов в моем контроллере.

@Operation(summary = "Verify API and backend connectivity",
        description = "Confirm connectivity to the backend, as well and verify API service is running.")
@OkResponse
@GetMapping(value = API_VERSION_2 + "/ping", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Ping> getPingV2(HttpServletRequest request) {

... }

А вот моя аннотация @OkResponse:

    @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ApiResponse(responseCode = HTTP_200,
        description = HTTP_200_OK,
        headers = {
                @Header(name = CONTENT_VERSION_HEADER, description = CONTENT_VERSION_HEADER_DESCRIPTION, schema = @Schema(type = "string")),
                @Header(name = DEPRECATION_MESSAGE_HEADER, description = DEPRECATION_MESSAGE_HEADER_DESCRIPTION, schema = @Schema(type = "string")),
                @Header(name = DESCRIPTION_HEADER, description = DESCRIPTION_HEADER_DESCRIPTION, schema = @Schema(type = "string"))
        })
public @interface OkResponse {
}

Мои сопоставления v1 определяются аналогично.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
0
2 596
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Таким образом, может показаться, что, полагаясь исключительно на OpenApiCustomiser для создания OpenAPI, отсканированные компоненты игнорируются или, по крайней мере, перезаписываются только компонентами, указанными в настройщике (я мог бы также программно добавить все свои схемы, но это было бы был очень громоздким в обслуживании).

Изменение моей конфигурации на следующее решило мою проблему:

@Bean
public OpenAPI customOpenAPI() {
    final String securitySchemeName = "Access Token";
    return new OpenAPI()
            .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
            .components(new Components().addSecuritySchemes(securitySchemeName, new SecurityScheme()
                    .type(SecurityScheme.Type.APIKEY)
                    .in(SecurityScheme.In.HEADER)
                    .name(HttpHeaders.AUTHORIZATION)))
            .info(apiInfo());
}

@Bean
public GroupedOpenApi apiV1() {
    String[] paths = {"/v1/**"};
    String[] packagesToScan = {"com.test.controller"};
    return GroupedOpenApi.builder()
            .group("v1")
            .packagesToScan(packagesToScan)
            .pathsToMatch(paths)
            .addOpenApiCustomiser(buildV1OpenAPI())
            .build();
}

@Bean
public GroupedOpenApi apiV2() {
    String[] paths = {"/v2/**"};
    String[] packagesToScan = {"com.test.controller"};
    return GroupedOpenApi.builder()
            .group("v2")
            .packagesToScan(packagesToScan)
            .pathsToMatch(paths)
            .addOpenApiCustomiser(buildV2OpenAPI())
            .build();
}

public OpenApiCustomiser buildV1OpenAPI() {
    return openApi -> openApi.info(openApi.getInfo().version("v1"));
}

public OpenApiCustomiser buildV2OpenAPI() {
    return openApi -> openApi.info(openApi.getInfo().version("v2"));
}

// Describe the apis
private Info apiInfo() {
    return new Info()
            .title("Title")
            .description("API Description.");
}

Хотя технически это также добавляет кнопку авторизации и схему безопасности в группу v1, ее можно игнорировать, поскольку эти конечные точки API в любом случае не защищены (внутренний API, и они все равно скоро исчезнут).

В любом случае, вероятно, это лучшее решение, поскольку информация в основном идентична между группами.

Вместо того, чтобы создавать новые компоненты, вы должны просто изменить их:

public OpenApiCustomiser buildV2OpenAPI() {
    final String securitySchemeName = "Access Token";
    return openApi -> {
        openApi.info(apiInfo().version("v2"))
            .addSecurityItem(new SecurityRequirement().addList(securitySchemeName));
        
        openApi.getComponents().addSecuritySchemes(securitySchemeName, new SecurityScheme()
            .type(SecurityScheme.Type.APIKEY)
            .in(SecurityScheme.In.HEADER)
            .name(HttpHeaders.AUTHORIZATION));

        return openApi;
    
    };
}

Исправлена ​​проблема без создания нового объекта компонентов, Пробовал с котлином.

  @Bean
  fun publicApiV1(): GroupedOpenApi = GroupedOpenApi.builder()
    .group("service-name")
    .pathsToMatch("/v1/**")
    .addOpenApiCustomiser(publicApiCustomizer("v1"))
    .build()

  fun publicApiCustomizer(version: String): OpenApiCustomiser? {
    return OpenApiCustomiser { openApi: OpenAPI ->
      openApi.addSecurityItem(SecurityRequirement().addList(SECURITY_SCHEME_NAME))
        .info(Info().title("title name").version(version))
        .also {
          it.components.addSecuritySchemes(
            SECURITY_SCHEME_NAME,
            SecurityScheme()
              .`in`(HEADER)
              .type(HTTP)
              .scheme(SCHEME)
              .name(SECURITY_SCHEME_NAME)
              .bearerFormat(BEARER_FORMAT)
          )
        }
    }
  }

  companion object {
    private const val SECURITY_SCHEME_NAME = "bearerAuth"
    private const val SCHEME = "bearer"
    private const val BEARER_FORMAT = "JWT"
  }

Другие вопросы по теме