Недавно я перешел с 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 определяются аналогично.




Таким образом, может показаться, что, полагаясь исключительно на 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"
}