노트

[SolveGO #12] 자신감 있게 뽐내며 걷기 - Swagger 도입 본문

Project/SolveGO

[SolveGO #12] 자신감 있게 뽐내며 걷기 - Swagger 도입

forwarder 2026. 7. 1. 14:47

 

 

 

 

 
 
 

 

 

지금까지 Backend MVP 7가지의 기능을 모두 구현 완료했다.

이제 Frontend 개발자에게 "노션에 API 명세 모두 작성해두었으니 그거 보고 API 호출 하시면 됩니다~"

 

라고 말한다면, 그건 프로가 아니다.

 

왜 그럴까?

왜 "노션에 API 기능 모두 작성해두었으니 그거 보고 API 호출 하시면 됩니다~"라고 하는게 프로가 아닐까?

 

내가 생각하는 이유는 다음과 같다.

첫째, API 명세를 노션에 따로 관리하면 코드와 문서가 서로 다른 방향으로 변할 수 있다.
요구사항이 바뀌어 서버 코드를 수정했는데, 노션 문서를 함께 수정하지 않으면 프론트엔드 개발자는 오래된 명세를 보고 API를 호출하게 된다. 즉, "코드와 문서 사이에 불일치"가 발생한다.

 

둘째, Frontend 개발자 입장에서 생각해 볼때, API를 테스트하기 위해 매번 curl 명령어를 작성하거나 Postman에 요청을 직접 구성하는 것은 번거롭다. API 명세를 보고 바로 요청 값을 확인하고, 버튼 하나로 실제 API 호출까지 할 수 있다면 훨씬 편하다.

 

 

만일 이 두가지 문제점을 해결하는 방법으로 Frontend 개발자에게 API 명세서를 줄 수 있다면,

자랑스럽게 우리가 한 작업을 넘겨 줄 수 있을 것이다.


이 두 문제를 해결하기 위해 Swagger UI와 OpenAPI 문서화를 도입하자.

springdoc-openapi는 Spring Boot 애플리케이션의 Controller, DTO, Validation, Swagger 애노테이션을 분석하여 OpenAPI 문서를 자동 생성한다. 그리고 Swagger UI는 이 OpenAPI 문서를 사람이 보기 좋은 화면으로 보여주며, 직접 API를 호출할 수 있는 테스트 환경까지 제공한다.

재미있게도 swagger라는 영어 단어에는 ‘자신감 있게 뽐내며 걷다’라는 뜻이 있다.

참고로 Swagger라는 이름은 실제로 swagger라는 영어 단어의 의미와 연결된다.

이전의 웹 API 문서화 환경이 매우 열악했기에, 개발자들이 "우리가 만든 훌륭한 API를 세상에 멋지게 뽐내고 쉽게 문서화할 수 있도록 만들자"는 의미를 담아 Swagger라는 이름을 붙인 것이다.



이제 구현한 API를 확인하고 테스트할 수 있는 문서로 만들어서 자신감 있게 뽐내며 Frontend 개발자에게 전달해주자.

 


 

 

어떤 문제를 풀 것인가

우리는 이미 구현이 완료된 Backend API를 문서화하려고 한다.

Frontend 개발자가 API의 URL, HTTP Method, Request Body, Response Body, 상태 코드, 인증 필요 여부를 빠르게 이해하고, 직접 호출까지 해볼 수 있어야 한다.

 

따라서 API 문서는 다음 조건을 만족해야 한다.

1. 서버 코드와 문서가 최대한 함께 관리되어야 한다.
2. 요청/응답 형식이 명확해야 한다.
3. 인증이 필요한 API와 공개 API가 구분되어야 한다.
4. 문서 화면에서 직접 API를 테스트할 수 있어야 한다.

 


 

 

어떻게 문제를 풀 것인가

Spring Boot에서는 springdoc-openapi를 사용해 이 문제를 해결할 수 있다.

springdoc-openapi 의존성을 추가하면, 애플리케이션 실행 시 Spring MVC에 등록된 Controller 정보를 분석하여 OpenAPI 명세를 자동 생성한다.

예를 들어 @GetMapping, @PostMapping, @RequestBody, @PathVariable 같은 Spring 애노테이션을 통해 API 경로, HTTP Method, 요청 형식, 응답 형식을 파악한다.

여기에 Swagger 애노테이션을 추가하면 문서를 더 읽기 좋게 보강할 수 있다.

@Tag
→ API 그룹 분류

@Operation
→ API 요약과 설명 작성

@ApiResponses
→ 응답 상태 코드와 의미 명시

@Schema
→ DTO 필드 설명과 예시 추가

@SecurityRequirement
→ JWT 인증이 필요한 API 표시
 

즉, 이번 작업의 방향은 다음과 같다.

1. springdoc-openapi 의존성을 추가한다.
2. Swagger UI와 OpenAPI 문서 엔드포인트를 확인한다.
3. Controller에 @Tag, @Operation, @ApiResponses를 추가한다.
4. DTO에 @Schema를 추가해 요청/응답 예시를 개선한다.
5. JWT 인증 설정을 Swagger 문서에 반영한다.
6. 공개 API와 보호 API를 구분한다.
7. Swagger UI에서 실제 API 호출까지 검증한다.

 

 


 

 

문제를 풀자

 

먼저 Swagger UI와 OpenAPI 문서를 사용하기 위해 build.gradle에 다음 의존성을 추가한다.

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

이 의존성을 추가하면 Spring Boot 애플리케이션에서 OpenAPI 문서를 자동 생성하고, Swagger UI 화면을 통해 해당 문서를 확인할 수 있다.

다음으로 Swagger UI와 OpenAPI 문서 엔드포인트는 인증 없이 접근할 수 있도록 SecurityConfig에 예외 경로를 추가한다.

 

.requestMatchers(
        "/swagger-ui/**",
        "/swagger-ui.html",
        "/v3/api-docs/**"
).permitAll()
 

여기서 각 경로의 의미는 다음과 같다.

/swagger-ui/**
→ Swagger UI 화면 리소스

/swagger-ui.html
→ Swagger UI 진입 경로

/v3/api-docs/**
→ OpenAPI 원본 문서 JSON
 

이제 서버를 실행한 뒤 아래 주소로 접속한다.

http://localhost:8080/swagger-ui.html
 

또는 다음 주소로도 접근할 수 있다.

http://localhost:8080/swagger-ui/index.html
 

접속하면 기본 Swagger UI 문서 화면이 나타난다.

 

 

하지만 아직 이 상태의 문서는 사람이 읽기에는 충분하지 않다.

API가 자동으로 목록에 나타나기는 하지만, API 그룹, 설명, 상태 코드, 요청/응답 예시, 인증 필요 여부가 명확하게 정리되어 있지 않다.

따라서 이제 Controller와 DTO에 Swagger/OpenAPI 애노테이션을 추가하여, Frontend 개발자가 보기 편한 API 문서로 개선해보자.

 

 

 

먼저 컨트롤러에 @Tag 애너테이션을 붙여 각 컨트롤러가 담당하는 API를 나타내자.

package com.kdh.solvego.domain.auth.controller;

//...

@Tag(name = "Auth", description = "인증 관련 API")
@RestController
@RequestMapping(
        value="/api/auth",
        produces = MediaType.APPLICATION_JSON_VALUE
)
public class AuthController {

    //...

}

 

 

 

 

그러면 Swagger UI에서 API가 Auth, User, Problem, Attempt와 같이 도메인별 그룹으로 정리되고, 각 그룹에 한국어 설명이 표시된다.

 

 

그리고 컨트롤러 메서드에도 @Operation 애너테이션을 붙여, 각 API의 역할과 동작에 대한 설명을 달아주자.

@Operation(
            summary = "로그인",
            description = "사용자 아이디와 비밀번호를 검증한 뒤 JWT access token을 발급합니다."
    )
    @PostMapping(
            value="/login"
    )
    public LoginResponse login(@Valid @RequestBody LoginRequest loginRequest) {
        return authService.login(loginRequest);
    }

 

 

이를 통해 Frontend 개발자는 API의 URL만 보는 것이 아니라, 각 API가 어떤 목적을 가지고 어떤 동작을 수행하는지 함께 확인할 수 있다.

 

 

 

또한 @ApiResponses 애너테이션을 붙여 각 API가 반환할 수 있는 HTTP 상태 코드와 응답 형식을 명시하자.

@ApiResponses({
            @ApiResponse(responseCode = "200", description = "로그인 성공"),
            @ApiResponse(
                    responseCode = "400",
                    description = "요청 형식 오류",
                    content = @Content(
                            mediaType = MediaType.APPLICATION_JSON_VALUE,
                            schema = @Schema(implementation = ErrorResponse.class)
                    )
            ),
            @ApiResponse(
                    responseCode = "401",
                    description = "아이디 또는 비밀번호 불일치",
                    content = @Content(
                            mediaType = MediaType.APPLICATION_JSON_VALUE,
                            schema = @Schema(implementation = ErrorResponse.class)
                    )
            )
    })

 


이 코드는 로그인 API는 성공 시 200 OK를 반환하지만, 요청 형식이 잘못된 경우 400 Bad Request를 반환하고, 아이디 또는 비밀번호가 일치하지 않는 경우 401 Unauthorized를 반환하는 것을 설명한다.

 

 

그러면 이렇게 swagger 문서에서 응답 Body의 형식과 HTTP 상태 코드에 대한 설명을 확인할 수 있게 된다.

 

 

 

이제 DTO에 @Schema 설명을 추가해 요청과 응답 Body의 필드 의미와 예시를 명확하게 만들어보자.

 

Swagger UI는 Controller 메서드의 요청 DTO와 응답 DTO를 자동으로 인식한다.

하지만 아무 설정을 하지 않으면 필드명이 단순히 string, 0, true 같은 기본 예시로 표시된다.

 

예를 들어 회원가입 요청 DTO가 다음과 같다고 해보자.

public record SignupRequest(
        @NotBlank
        @Size(min = 6, max = 20)
        String username,

        @NotBlank
        String password
) {
}

이 상태에서도 Swagger UI는 username, password 필드가 존재한다는 것은 보여준다.

하지만 각 필드가 어떤 의미인지, 어떤 값이 들어가야 하는지까지는 충분히 설명하지 못한다.

 

따라서 @Schema 애너테이션을 사용해 DTO와 각 필드에 설명과 예시를 추가한다.

@Schema(description = "회원가입 요청")
public record SignupRequest(

        @Schema(description = "사용자 아이디", example = "solvego123")
        @NotBlank
        @Size(min = 6, max = 20)
        String username,

        @Schema(description = "비밀번호", example = "password1234")
        @NotBlank
        String password
) {
}

@Schema의 description은 해당 DTO나 필드가 어떤 의미를 가지는지 설명하고, example은 Swagger UI에서 보여줄 예시 값을 지정한다.

 

 

 

이렇게 설정하면 Swagger UI의 Request Body 예시가 위와 같이 더 실제 API 요청에 가까운 형태로 표시된다.

 

 

 


 

 

 

한편, 우리가 작성한 API 중에는 로그인이 완료된 상태, 즉 JWT 토큰을 헤더에 포함시킨 요청만 접근할 수 있도록 하는 API들이 있었다.

그러나 현재 Swagger UI 에서 Test 버튼을 누를 때에는, JWT 토큰을 헤더에 포함시키는 로직이 없다.

따라서 보호 API를 Swagger UI에서 호출하면 Authorization 헤더 없이 요청이 전송되고, 서버의 Spring Security, JWT 필터에서 401 Unauthorized 응답을 반환한다.

 

문제 등록 API에 접근 할 수 없는 모습

 

 

 

 

이를 위해서 OpenAPI 문서에 JWT Bearer 인증 방식을 등록하는 코드를 작성하자.

package com.kdh.solvego.global.config;

//...
@Configuration
public class OpenApiConfig {

    private static final String SECURITY_SCHEME_NAME = "bearerAuth";

    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI()
                .components(new Components()
                        .addSecuritySchemes(
                                SECURITY_SCHEME_NAME,
                                new SecurityScheme()
                                        .type(SecurityScheme.Type.HTTP)
                                        .scheme("bearer")
                                        .bearerFormat("JWT")
                        )
                );
    }
}

 

이 설정을 추가하면 Swagger UI 상단 좌측에 Authorize 버튼이 나타난다.

이제 로그인 API를 호출해 발급받은 JWT access token을 Authorize 버튼에 입력하면, Swagger UI에서 인증이 필요한 API를 테스트할 준비가 된다.

 

단, 여기까지는 JWT 인증 방식을 Swagger 문서에 등록한 것일 뿐이다.

특정 API가 이 인증 방식을 필요로 한다는 것은 각 보호 API에 @SecurityRequirement를 추가해서 표시해야 한다.

 

 

예를 들어, 틀린 문제 조회 API의 경우는 JWT 인증 방식을 필요로 하기 때문에, @SecurityRequirement를 추가해주어야 한다.

@SecurityRequirement(name = "bearerAuth")
@GetMapping("/me/wrong-problems")
public List<WrongProblemResponse> getWrongProblems(
        Authentication authentication
) {
    Long userId = (Long) authentication.getPrincipal();

    return attemptService.getWrongProblems(userId);
}

 

 

 

그러면 보호 API의 경우는 오른쪽에 자물쇠 모양이 뜨면서 보호 API임을 시각적으로 알려주는 것을 확인할 수 있다.

 

 

이를 테스트 하기 위해 Authorize 버튼을 클릭하여, 로그인 API를 호출해 JWT를 발급받고 Authorize 버튼에 입력하여 Swagger UI가 요청 헤더에 JWT를 포함하도록 설정하자.

 

 

그러면 위와 같이 문제 등록 API 또한 401 Unauthorized가 아니라 201 Created가 반환되는 것을 확인할 수 있다.

 


 

이렇게 해서 Frontend 개발자와 협업할 수 있는 API 명세서를 완성했다.

Swagger UI를 통해 API 목록, 요청과 응답 형식, 상태 코드, 인증 필요 여부를 한 화면에서 확인할 수 있게 되었고,

로그인 후 JWT가 필요한 보호 API도 직접 테스트할 수 있게 되었다.

 

또한 API 명세를 노션에 따로 관리하는 방식이 아니라,

Controller와 DTO를 기반으로 OpenAPI 문서가 생성되도록 구성했기 때문에 코드와 문서 사이의 불일치 가능성도 줄일 수 있다.

 

다음 글에서는 지금까지 Postman, Swagger UI에서 수동으로 확인했던 API 동작을 테스트 코드로 고정하여, 이후 기능이 변경되거나 깨졌을 때 바로 발견할 수 있는 기반을 만들어보자.