Spring

[Spring] OpenApI 3.0 Swagger 문서 작성 및 설정 방법

기억은 RAM, 기록은 HDD 2025. 2. 11. 15:23

들어가며

Swagger 는 RestDocs 에 비해 단순 적용은 간편하지만 기본적인 설정만으로 사용하기에는 아쉬운 부분이 있다. 이번 글에서는 Open API 3.0 Swagger 를 적용할때 필요한 개념과 조금 더 나은 문서를 만들기 위한 몇가지 노력들을 적어본다.

 

Spring을 사용한다면 아래 2개의 라이브러리를 사용할 수 있다.

  • Spring Fox
  • Spring Doc

Spring Fox의 경우 마지막 업데이트일인 2020년 이후 업데이트가 없으므로, Spring Doc을 사용했다.

 

Springdoc-openapi Modules 

SpringDoc OpenAPI는 Spring Boot 뿐만 아니라, WebFlux 도 지원하며 UI 레이어REST API 레이어로 구분되어 구성된다.

UI 레이어는 API 문서를 웹 UI로 렌더링하는 역할을 한다.

  • springdoc-openapi-starter-webmvc-ui / springdoc-openapi-starter-webflux-ui
    • Swagger UI를 제공하는 모듈
    • Spring MVC(webmvc-ui)와 Spring WebFlux(webflux-ui)에 따라 다르게 제공
  • swagger-ui
    • Swagger의 핵심 UI 모듈
    • OpenAPI 스펙(JSON 또는 YAML)을 기반으로 웹 UI를 생성

REST API 레이어는 API 문서를 생성하는 역할을 한다.

  • springdoc-openapi-starter-webmvc-api / springdoc-openapi-starter-webflux-api
    • OpenAPI 스펙을 자동 생성하는 모듈
    • spring-webmvc(Spring MVC) 또는 spring-webflux(Spring WebFlux)에 따라 다른 모듈이 사용
  • springdoc-openapi-starter-common
    • WebMVC와 WebFlux 공통으로 사용하는 OpenAPI 기능을 제공
  • swagger-core-jakarta
    • Swagger의 코어 기능을 제공
    • OpenAPI 문서의 JSON/YAML 스펙을 처리
  • spring-boot-autoconfigure
    • Spring Boot에서 OpenAPI 관련 설정을 자동으로 적용

따라서 SpringDoc OpenAPI가 애플리케이션의 REST API를 자동으로 분석하고 API의 요청/응답 모델을 기반으로 OpenAPI 문서(JSON, YAML)를 생성 하면 (기본적으로 /v3/api-docs 엔드포인트) Swagger UI가 OpenAPI 문서를 가져와 렌더링하는 구조로 이루어진다.

 

기본적으로 JSON 과 YAML 형식의 OpenAPI 문서가 모두 자동으로 생성되며, 다음 엔드포인트에서 확인할 수 있다.

  • JSON: http://localhost:8080/v3/api-docs
  • YAML: http://localhost:8080/v3/api-docs.yaml

설정

build.gradle 에 Springboot 버전별 호환되는 버전을 넣어준다. 

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'

 

세부 Property 변경이 필요하다면 application.yml 에서 변경할 수 있다. 공식문서에서 더 자세히 볼 수 있다.

springdoc:
  api-docs:
    enabled: true  # API 문서(`/v3/api-docs`) 활성화 여부 (기본값: true)
    path: /v3/api-docs  # API 문서의 엔드포인트 변경 (기본값: /v3/api-docs)
    groups.enabled: true  # 여러 개의 API 그룹을 지원할지 여부

  swagger-ui:
    enabled: true  # Swagger UI 활성화 여부 (기본값: true)
    path: /swagger-ui.html  # Swagger UI 접속 경로 변경 (기본값: /swagger-ui.html)
    operationsSorter: method  # API 정렬 기준 (alpha: 알파벳순, method: HTTP 메서드 순)
    tagsSorter: alpha  # 태그 정렬 방식 (alpha: 알파벳순 정렬)
    display-request-duration: true  # 요청 실행 시간 표시 여부
    doc-expansion: none  # Swagger UI에서 API 설명의 기본 펼침 상태 (none: 닫힘, list: 펼침, full: 전체 펼침)
    persistAuthorization: true  # 페이지 새로고침 후에도 Authorization 헤더 유지 여부
    defaultModelsExpandDepth: -1  # 모델 스키마의 기본 펼침 깊이 (-1이면 모델 펼쳐지지 않음)

  paths-to-match:  # OpenAPI 문서에서 포함할 엔드포인트 패턴
    - /api/**  # /api로 시작하는 모든 경로 포함
    - /admin/**  # /admin으로 시작하는 모든 경로 포함
  cache:
    disabled: true  # OpenAPI 문서의 캐싱 비활성화 (기본값: false)
  show-actuator: false  # Spring Actuator 엔드포인트를 Swagger UI에 표시할지 여부 (기본값: false)
  servers:
    - url: http://localhost:8080  # 기본 서버 URL 설정
      description: "Local 개발 환경"
    - url: https://api.example.com  # 운영 서버 URL 설정
      description: "Production 서버"

 

자주 사용되는 어노테이션

@Operation 

API 엔드포인트의 제목 및 설명을 정의할 수 있다.

@Operation(summary = "유저 정보 조회", description = "유저의 상세 정보를 가져옵니다.")​

 @Tag

Tag에 설정된 name 이 같은 것끼리 하나의 api 그룹으로 묶인다. 

  • name : 태그의 이름
  • description : 태그에 대한 설명
@Tag(name = "posts", description = "게시물 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/post")
public class PostController {
...
}

@Components

공통적으로 사용할 Schema, Security, Parameter 등을 정의할 수 있다. (주로 Swagger Config 에서 설정한다)

@ApiResponses

여러 응답을 묶어서 표시하기 위해 @ApiResponse 와 함께 사용된다. 

@ApiResponse

개별 응답을 정의할 때 사용된다.

  • responseCode: HTTP 상태 코드 ex) "200", "400", "500"
  • description: 응답에 대한 설명 ex) "조회 성공", "잘못된 요청"
  • content: 응답 데이터의 미디어 타입과 스키마를 정의하는 @Content 설정

@Content

API 응답에서 데이터의 형식과 스키마를 정의하는 어노테이션이다.
@ApiResponse 내부에서 사용되며, 응답의 mediaType과 schema 정보를 포함한다.

  • mediaType: 응답 데이터의 미디어 타입. ex) "application/json"
  • schema: 전체 응답 객체를 정의하는 @Schema
  • schemaProperties: JSON의 개별 필드를 @SchemaProperty로 정의

@ApiResponses({
    @ApiResponse(responseCode = "200", description = "조회 성공",  
        content = @Content(
            mediaType = "application/json",
            schemaProperties = {
                    @SchemaProperty(name= "result", schema = @Schema(example = "SUCCESS")),
                    @SchemaProperty(name="data", schema  = @Schema(implementation = ProfileResponse.class))
            }
        )
    ),
    ... 
 })

@SchemaProperty

응답 JSON의 특정 필드에 대한 설명을 추가하는 어노테이션으로 JSON 객체 내부의 하위 필드를 설명할 때 유용하다.

  • name: 응답 데이터에서 필드의 이름. ex) "result", "data" 
  • schema: 필드의 스키마 정의를 위한 @Schema 설정

@Schema

Swagger UI에서 API 요청 및 응답 데이터 모델을 정의하는 데 사용된다. implement 를 이용해 특정 DTO 클래스를 지정해 설정할 수도 있다.  @SchemaProperty는 비슷하지만, 적용 대상이 다르다. @SchemaProperty 가 API 응답 내부의 특정 필드에 사용된다면, @Schema 는 DTO 클래스 및 필드에 모두 설정할 수 있다.

  • description: 필드 또는 클래스에 대한 설명을 제공
  • example: 예제 값을 설정
  • required: 필수 필드 여부를 지정
  • defaultValue: 기본값을 설정.
  • implementation: 특정 DTO 클래스를 지정하여 스키마를 정의

필드에 @Schema 적용

각 필드에 설명을 추가할 수 있다.

public class UserDto {

    @Schema(description = "사용자의 이름", example = "홍길동", minLength = 2, maxLength = 10)
    private String name;

    @Schema(description = "사용자의 이메일 주소", example = "hong@example.com", format = "email")
    private String email;

    @Schema(description = "사용자의 나이", example = "25", minimum = "18", maximum = "99")
    private int age;
}

 

Enum 타입의 경우, @Schema의 enumAsRef 또는 allowableValues를 사용하여 문서화할 수 있다.

public enum UserRole {
    ADMIN, USER, GUEST;
}

public class UserDto {

    @Schema(description = "사용자의 역할", example = "USER", allowableValues = {"ADMIN", "USER", "GUEST"})
    private UserRole role;
}​
 

implementation을 사용하여 특정 DTO를 참조

다른 DTO를 포함하는 경우 implementation을 사용하여 OpenAPI 문서에서 DTO 간의 관계를 정의할 수 있다.

public class UserResponse {

    @Schema(description = "응답 상태", example = "SUCCESS")
    private String status;

    @Schema(description = "사용자 정보", implementation = UserDto.class)
    private UserDto data;
}

@Hidden

특정 API 를 숨길 때 사용한다.

@Parameter(hidden = true)

특정 파라미터를 숨겨야할 때 사용한다. 예를 들자면, HandlerMethodArgumentResolver 를 이용해 파라미터로 받는 객체가 있을 때

기본적으로 swagger 에 표시되므로, 해당 파라미터를 숨기도록 설정할 수 있다.

Swagger Config 설정

OpenAPI 클래스

Swagger Config 를 만들때 OpenApI 클래스에서 제공하는 속성을 변경할 수 있다.

  •  ex) servers 에 테스트 서버와 실제 운영 서버 주소를 추가하고 API 서버 선택 드롭다운 메뉴에 나타나게 구성
openapi OpenAPI 명세 버전 (3.0.1 또는 3.1.0)
info API 문서의 제목, 설명, 버전 등의 정보를 담는 Info 객체
externalDocs API 문서 외부 링크를 제공하는 ExternalDocumentation 객체
servers API 서버 목록을 정의하는 List<Server>
security 보안 요구사항을 정의하는 List<SecurityRequirement>
tags API 그룹을 정의하는 List<Tag>
paths API 경로와 엔드포인트를 관리하는 Paths 객체 (Map<String, PathItem>)
components API의 재사용 가능한 요소(스키마, 보안 스키마, 파라미터 등)를 정의하는 Components 객체
extensions OpenAPI 확장을 위한 Map<String, Object> (커스텀 메타데이터 저장)
jsonSchemaDialect OpenAPI 3.1에서 JSON Schema 다이얼렉트를 지정
specVersion OpenAPI 명세의 버전 (SpecVersion.V3_0_1, SpecVersion.V3_1_0)
webhooks OpenAPI 3.1에서 Webhook 이벤트를 정의하는 Map<String, PathItem>

Security Schema 설정 (JWT)

Security Schema를 이용해 Swagger에서 Authorization Header에 JWT 토큰을 입력할 수 있도록 설정할 수 있다.

@Bean
public OpenAPI openAPI() {
    SecurityScheme securityScheme = new SecurityScheme()
            .type(SecurityScheme.Type.HTTP)
            .scheme("bearer")
            .bearerFormat("JWT");
    SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth");

    OpenAPI openApi = new OpenAPI()
            .components(new Components().addSecuritySchemes("bearerAuth", securityScheme))
            .addSecurityItem(securityRequirement)
            ...

 

설정하면, 실제 API 요청 시 Authorization 헤더가 자동으로 추가된다. 아래 설정을 추가하면 모든 엔드포인트에 적용된다.

.addSecurityItem(securityRequirement); // 모든 API에 적용

 

특정 API 에만적용하거나, 적용하지 않을 수도 있다.

// 특정 엔드포인트에서만 적용되도록 설정
@Operation(summary = "내 프로필 조회", security = @SecurityRequirement(name = "Bearer Token"))

// name 속성을 빈 문자열로 설정하면 해당 엔드포인트에서 JWT 인증이 필요하지 않도록 설정
@Operation(summary = "메인페이지", security = @SecurityRequirement(name = ""))

 

Security Schema 가 적용된 엔드포인트는 API 문서에서 자물쇠 표시가 보이게 된다.

 

OpenApiCustomizer

OpenApiCustomizer를 사용해 OpenAPI 문서를 코드 레벨에서 동적으로 수정할 수 있다.

예를들어, Tag 들의 순서를 원하는대로 맞추고 싶을 때 아래처럼 적용할 수 있다.

@Bean
public OpenApiCustomizer customOpenAPI() {
    List<String> tagOrder = List.of(
            "인증", "약관", ... );

    return openApi -> openApi.setTags(
            openApi.getTags().stream()
                    .sorted(Comparator.comparingInt(tag -> IntStream.range(0, tagOrder.size())
                            .filter(i -> tag.getName().contains(tagOrder.get(i)))
                            .findFirst()
                            .orElse(tagOrder.size())))
                    .toList()
    );
}

 

 

비슷한 방식으로 Schema 정렬도 가능하다. 

   return openApi -> {
            Map<String, Schema> schemas = openApi.getComponents().getSchemas();
            openApi.getComponents().setSchemas(new TreeMap<>(schemas));
        };

응답 형식 추가

Swagger 는 description 에 html, markdown 을 삽입할 수 있다. 공통응답이나 기재해야할 링크가 있는 경우 활용할 수 있다.

컨트롤러에 붙은 어노테이션을 인터페이스로 분리

문서화을 하다보면 컨트롤러에 Swagger 어노테이션이 많이 붙어있어, 가독성이 떨어지고 보기에도 그닥 좋지 않다. 별도 인터페이스를 만들어 분리해줄 수 있다. 

 

 

인터페이스의 default 메서드를 이용해 Security 경로 추가

SpringSecurity 에서 UsernamePasswordAuthenticationFilter 를 이용해 로그인을 적용하면 기본적으로 Swagger 에 인식되지 않는다. SecurityFilter 는 MVC 컨트롤러에 도달하기 전에 처리가 이루어지므로, API 응답 표시용으로 default 메서드를 이용해 나타낼 수 있다.

PathItem 을 이용해 OAuth2 소셜 로그인 경로 추가

Spring Security OAuth2 Client 를 이용할 경우,  api 주소를 명시적으로 적어줄 수 있다.

 

API Docs 내보내기

서버를 키지 않았을 때 API 문서를 보거나 전달하기 위해 Swagger 의 API 들을 Export 할 수 있다. 

YAML 기본 경로 http://localhost:8080/v3/api-docs.yaml 에서 yaml 을 다운로드받은 뒤 Swagger 에디터에 들어가서 붙여넣는다.

 

html2 를 선택하고 (html1 보다 UI 가 좋다) 다운받는다. 

 

 

확인해보면 제대로 변환된 것을 볼 수 있다.