💐 Spring

Springdoc을 이용한 API 문서 자동화 (Swagger, SpringBoot 3.x)

iseunghan 2023. 6. 8. 21:30
반응형

Spring Boot 3.x.x 부터는 JavaEE → Jakarta EE로 교체되었습니다.

그렇기 때문에 이전에 사용했던 Springfox는 현재 Spring Boot 3.x.x에 대한 업데이트가 이뤄지지 않고 현시점에는 사용할 수 없었습니다. Springdoc 공식문서에서 어떻게 적용할 수 있는지에 대한 자세한 방법이 나와있으니 참고하시면 좋을 것 같습니다.

springdoc-openapi v2.0.2

의존성 추가

Spring Boot 3.x.x부터는 아래 의존성을 추가해주면 Swagger-ui 설정은 끝입니다.

implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2"
  • springdoc-openapi-ui → springdoc-openapi-starter-webmvc-ui 로 교체되었습니다.
  • springdoc-openapi-webflux-ui → springdoc-openapi-starter-webflux-ui 로 교체되었습니다.

해당 의존성을 추가해주는 것만으로도 /swagger-ui.html 접근이 가능합니다.

설정 파일

현재 API 문서의 설명이나, 버전 등등 더 세부적으로 설정해줄 순 없을까요?

바로 OpenApi를 빈으로 등록하는 방법이 있습니다.

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class SpringdocConfig {

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .addOpenApiCustomizer(openApi -> openApi
                        .info(new Info().title("TEST API DOCS")
                                .description("테스트 API 문서입니다.")
                                .version("v1.0"))
                .group("Test Group")
                .packagesToScan("org.example.controller")
                .displayName("This is Test API")
                .build();
    }
}

application.yml 설정

springdoc:
  packages-to-scan: org.example.controller
  default-consumes-media-type: application/json;charset=UTF-8
  default-produces-media-type: application/json;charset=UTF-8
  swagger-ui:
    path: /docs/test.html
    tags-sorter: alpha
    operations-sorter: alpha
		syntax-highlight:
      activated: true
  api-docs:
    path: /v3/docs
    groups:
      enabled: true
  cache:
    disabled: true
  • springdoc.swagger-ui.tags-sorter: URI를 다음 기준으로 정렬
  • springdoc.swagger-ui.operations-sorter : HTTP 메소드를 다음 기준으로 정렬
    • alpha: 알파벳 순으로 정렬
    • method: HTTP 메소드 순으로 정렬

더 많은 properties 설정은 공식문서에서 확인하시길 바랍니다.

Spring Security를 사용하고 있다면?

implementation 'org.springdoc:springdoc-openapi-security:1.6.14'

이미 springdoc-openapi-starter-webmvc-ui 의존성을 사용중이라면, 따로 의존성을 추가해줄 필요는 없습니다. 우리가 추가해줬던 의존성으로 인해 @AuthenticationPrincipal 어노테이션은 자동으로 swagger-ui에 ignore 되므로 따로 처리해주지 않아도 됩니다.

Javadoc 으로 문서를 꾸며보자

springdoc-openapi-starter-webmvc-ui 의존성을 사용중이라면, 아래 의존성들을 추가해줘서 javadoc을 활성화 시켜줘야 합니다.

annotationProcessor 'com.github.therapi:therapi-runtime-javadoc-scribe:0.13.0'
implementation 'com.github.therapi:therapi-runtime-javadoc:0.13.0'

이제 작성한 javadoc은 다음과 같이 처리됩니다.

  • method comment: @Operation(description={}) 으로 치환됩니다.
  • @return: @Operation(response={}) 으로 치환됩니다.
  • attribute comment: @Schema(description={}) 으로 치환됩니다.
💡 만약 Swagger Annotation과 javadoc이 동시에 존재한다면, Swagger Annotation이 우선으로 적용됩니다.

Authorization 추가

JWT를 사용해 인가를 하고있다면 다음과 같이 설정을 추가하면 Swagger에서 전역 인증정보를 설정하여 인증이 필요한 API를 호출할 수 있습니다.

@Configuration
public class SpringdocConfig {

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .addSecurityItem(new SecurityRequirement().addList("Authorization"))
                .components(new Components().addSecuritySchemes("Authorization", new SecurityScheme()
                        .name("Authorization")
                        .type(SecurityScheme.Type.HTTP)
                        .scheme("bearer")
                        .bearerFormat("JWT")))
                .addOpenApiCustomizer(openApi -> openApi
                        .info(new Info().title("TEST API DOCS")
                                .description("테스트 API 문서입니다.")
                                .version("v1.0"))
                .group("Test Group")
                .packagesToScan("org.example.controller")
                .displayName("This is Test API")
                .build();
    }
}

공통 Pageable 응답 어노테이션 개발

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Parameters({
        @Parameter(name = "page", description = "페이지 번호 (0..N) [기본값: 0]", schema = @Schema(type = "integer", defaultValue = "0", nullable = true)),
        @Parameter(name = "size", description = "페이지 번호 (0..100) [기본값: 10]", schema = @Schema(type = "integer", defaultValue = "10")),
        @Parameter(name = "sort", description = "정렬 (컬럼,asc|desc) [예시] 이름을 내림차순 정렬 ex) 'name,desc'", schema = @Schema(type = "array", name = "정렬 (컬럼,asc|desc) [예시] 이름을 내림차순 정렬 ex)")),
        @Parameter(name = "pageable", hidden = true)
})
public @interface ApiPageable {
}

공통 API 응답 어노테이션 개발

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ApiResponse(responseCode = "401", description = "UnAuthorized", useReturnTypeSchema = true, content = @Content(schema = @Schema(implementation = YourExample.class), mediaType = APPLICATION_JSON_VALUE, examples = @ExampleObject(value = "example")))
@ApiResponse(responseCode = "403", description = "Forbidden", useReturnTypeSchema = true, content = @Content(schema = @Schema(implementation = YourExample.class),mediaType = APPLICATION_JSON_VALUE, examples = @ExampleObject(value = "example")))
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(schema = @Schema(implementation = YourExample.class)))
public @interface ApiCommonResponse {
}

Controller 명세

GET 방식

@Operation(
        summary = "회원 조회 API",
        description = "회원을 조회할 수 있습니다.",
        responses = {
                @ApiResponse(responseCode = "200", description = "Success", useReturnTypeSchema = true, headers = @Header(name = "Authorization", required = true), content = @Content(schema = @Schema(implementation = YourExample.class), mediaType = APPLICATION_JSON_VALUE, examples = @ExampleObject(value = "example"))),
                @ApiResponse(responseCode = "404", description = "NotFound", useReturnTypeSchema = true, content = @Content(schema = @Schema(implementation = YourExample.class), examples = @ExampleObject(value = "example"))),})
@Parameters({
        @Parameter(name = "username", description = "사용자 이름", example = "john", required = true),
        @Parameter(name = "age", description = "사용자 나이", example = "20", required = true)
})
@ApiPageable
@ApiCommonResponse
@GetMapping("/members")
ResponseEntity<MemberDto> searchMembers(
        @RequestParam("username") String username,
        @RequestParam("age") int age,
        @PageableDefault Pageable pageable
){
	...
}

POST 방식

@Operation(summary = "회원을 생성하는 API",
        requestBody = @RequestBody(description = "회원 생성을 위한 DTO"),
        responses = {
                @ApiResponse(responseCode = "200", description = "Success", useReturnTypeSchema = true, headers = @Header(name = "Authorization", required = true), content = @Content(schema = @Schema(implementation = CreateProjectResponse.class), mediaType = APPLICATION_JSON_VALUE, examples = @ExampleObject(value = "{\\"success\\":true,\\"error\\":null,\\"message\\":{\\"projectId\\":1}"))),
                ...
        })
@ApiCommonResponse
ResponseEntity<Long> createProject(
        @Valid @RequestBody CreateMemberDto request
) {
	...
}

 

 

REFERENCES

 

springdoc-openapi v2.1.0

springdoc-openapi java library helps to automate the generation of API documentation using spring boot projects. springdoc-openapi works by examining an application at runtime to infer API semantics based on spring configurations, class structure and vario

springdoc.org

 

Set JWT with Spring Boot and Swagger UI | Baeldung

Learn how to set a JSON Web Token on requests to Swagger UI running in Spring Boot.

www.baeldung.com

반응형