Spring Boot3, Swagger (feat. API 문서 자동 생성)

작성일

삼쩜삼 과제 테스트를 진행하면서 처음 알게 된 Swagger에 대해 정리해보았다. 기존에 API 문서를 생성할 땐 Spring REST Docs를 사용해서 작성한 경험이 있다. Spring REST Docs와 Swagger에 대해 알아보고 Swagger 사용법에 대해서 알아보자.

Spring REST Docs

Spring REST Docs는 테스트 코드를 기반으로 API 문서를 작성할 수 있다. 테스트를 통과한 API만 문서화되기 때문에 안정성을 보장한다. 하지만, 테스트 코드 아래에 이어 붙이는 형식으로 지원하기 때문에 테스트 코드의 양이 많아진다.

Swagger

Swagger는 API 문서 작업을 자동으로 해주는 라이브러리이다. 컨트롤러 등 Production 코드를 바탕으로 API 문서를 작성해준다. 하지만, Production 코드에 Swagger 관련 코드가 들어가기 때문에 코드가 지저분해진다는 단점이 있다.

Swagger는 springdoc와 springfox 라이브러리가 있는데 springfox는 2.7.x 버전까지만 적용할 수 있고 Spring Boot 3 부터는 springdoc을 사용해야 한다고 한다.

dependency 추가

먼저 Swagger를 사용할 때 필요한 Dependency를 추가한다.

// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
springdoc:
  swagger-ui:
    # path: /swagger-ui.html # Swagger API 문서 주소 설정
    operationsSorter: method
    disable-swagger-default-url: true
    display-request-duration: true
  show-actuator: true
  default-consumes-media-type: application/json
  default-produces-media-type: application/json

Swagger 설정 파일

JWT 토큰을 사용하기 때문에 이와 관련된 설정도 추가해주었다.

package com.szs.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@OpenAPIDefinition(info = @Info(title = "API 명세서", version = "v1"))
@RequiredArgsConstructor
@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI openAPI() {
        String jwtSchemeName = "JWT Token";

        SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName);

        Components components = new Components()
                .addSecuritySchemes(jwtSchemeName, new SecurityScheme()
                        .name(jwtSchemeName)
                        .type(SecurityScheme.Type.HTTP)
                        .scheme("bearer")
                        .bearerFormat("JWT"));

        return new OpenAPI()
                .components(components)
                .addSecurityItem(securityRequirement);
    }
}

JWT Token 사용 예시

curl -X 'GET' \
  'http://localhost:8080/szs/me' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer xxxx' // JWT Token 설정

컨트롤러

package com.szs.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/szs")
public class UserController {

    private final UserService userService;

    @Tag(name = "회원가입, 로그인")
    @Operation(summary = "로그인", description = "로그인을 성공하면 토큰을 반환합니다.")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "OK"),
            @ApiResponse(responseCode = "404", description = "NOT FOUND",
                    content = @Content(schema = @Schema(implementation = Response.class))),
            @ApiResponse(responseCode = "409", description = "UNAUTHORIZED",
                    content = @Content(schema = @Schema(implementation = Response.class))),
            @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR",
                    content = @Content(schema = @Schema(implementation = Response.class)))
    })
    @PostMapping("/login")
    public Response<UserLoginResponse> login(
            @Parameter(description = "로그인 시 필요한 요청 정보", required = true, content = @Content(schema = @Schema(implementation = UserLoginRequest.class)))
            @RequestBody UserLoginRequest request) {
        String token = userService.login(request.toDto(request));
        return Response.success(new UserLoginResponse(token));
    }
}
  • @Tag: Tag에 설정된 name이 같은 것 끼리 하나의 API 그룹으로 묶는다.
  • @Operation: 해당 API가 어떤 리소스를 나타내는지 간략한 설명을 추가할 수 있다.
  • @ApiResponses, @ApiResponse: API의 Response 정보를 나타낼 수 있다. @ApiResponses는 여러 Response 들을 묶어 사용할 수 있고, @ApiResponse는 단일 Response에 대한 정보를 나타낸다.

1

2

3

Request Body 정보 명세

package com.szs.controller.request;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "로그인 요청 정보")
public record UserLoginRequest(
        @Schema(description = "아이디", example = "hong12")
        String userId,
        @Schema(description = "패스워드", example = "password1213")
        String password) {
}
  • @Schema: 클래스 레벨에 사용하면 해당 클래스가 어떤 클래스인지 설명을 작성할 수 있고, 필드에 사용하면 각 필드에 대한 설명, 예시, 필수 값 여부를 추가할 수 있다.

4

JWT 토큰

JWT 토큰이 필요한 경우 아래 그림과 같이 초록색 버튼을 클릭 후 토큰을 입력한 후 요청을 진행하면 된다.

5

Swagger 기본 주소

Swagger의 기본 주소는 http://localhost:8080/swagger-ui/index.html 이다.

만약 주소를 변경하고 싶다면 yml 설정 파일에서 변경하면 된다.

springdoc.swagger-ui.path: /swagger-ui.html

마치며

Spring REST Docs와 Swagger를 모두 사용해본 경험으로는 Swagger보단 Spring REST Docs를 사용하는 것이 좋은 것 같다. 일단, 컨트롤러 코드에 Swagger 관련 코드를 사용하는 것이 너무 지저분하다..🥲 Spring REST Docs를 사용하면 테스트 코드를 작성하는 겸.. 사용할 수 있으니 장점이지 않을까 하는 생각을 한다.