Spring Rest Docs + Swagger를 같이 사용해보자!

General Dong·2024년 12월 14일
0

Spring REST Docs

목록 보기
5/5
post-thumbnail

최근 간단한 프로젝트에서 Swagger를 처음 사용해보았다. 나는 Spring Rest DocsPostman에서 제공하는 API 명세서만 사용해봤는데, 확실히 디자인이나 기능적인 측면에서 Swagger가 괜찮았다. 심지어 Postman으로 옮길 수도 있어서 활용 방식은 뛰어나다고 생각한다.

그래서 이전까진 Spring Rest DocsSwagger를 동시에 사용하는 건 딱히 생각이 없었지만, 이번에 해보려고 한다! 테스트 강제해서 API 명세서의 신뢰성을 높이는 Spring Rest Docs의 장점과 깔끔한 디자인, API 테스트 기능 등을 제공하는 Swagger의 장점을 합쳐서 사용해보려고 한다.

Swagger의 가장 큰 단점이었던 main 코드에 어노테이션을 덕지덕지 붙이는 것을 보완할 수 있는 방법이라 굉장히 매력적으로 다가왔다! 그럼 이제 기존 Spring Rest DocsSwagger를 같이 사용하는 방법을 알아보자!

로컬 환경 : Spring Boot 3.2.3, Java 17, Windows 10

build.gradle

// GenerateSwaggerUI import
import org.hidetake.gradle.swagger.generator.GenerateSwaggerUI

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
    
    // openAPI 플러그인 추가
    id 'com.epages.restdocs-api-spec' version '0.18.2'
    // swaggerUI 플러그인 추가
    id 'org.hidetake.swagger.generator' version '2.18.2'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

// 생성된 API 스펙이 어느 위치에 있는지 지정
swaggerSources {
    sample {
        setInputFile(file("${buildDir}/api-spec/openapi3.yaml"))
    }
}

dependencies {

	... 생략

    // REST Docs
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
    // 8. openAPI3 추가
    testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.19.4'
    // 9. SwaggerUI 추가
    swaggerUI 'org.webjars:swagger-ui:5.18.2'
}

// OpenAPI3 설정
openapi3 {
    server = 'http://localhost:8080'
    title = "API 문서"
    description = "RestDocsWithSwagger Docs"
    version = "0.0.1"
    format = "yaml"
}

tasks.named('test') {
    useJUnitPlatform()
}

// JWT (Bearer Token)를 Authorization 헤더에 명시할 수 있게 설정
tasks.withType(GenerateSwaggerUI) {
    dependsOn 'openapi3'
    
    doFirst {
        def swaggerUIFile = file("${openapi3.outputDirectory}/openapi3.yaml")

        def securitySchemesContent =  "  securitySchemes:\n" +  \
                                      "    bearerAuth:\n" +  \
                                      "      type: http\n" +  \
                                      "      scheme: bearer\n" +  \
                                      "      bearerFormat: JWT\n" +  \
                                      "      name: Authorization\n" +  \
                                      "      in: header\n" + \
                                      "      description: \"Use 'your-access-token' as the value of the Authorization header\"\n" + \
                                      "security:\n" +
                "  - bearerAuth: []  # Apply the security scheme here"

        swaggerUIFile.append securitySchemesContent
    }
}

// 생성된 Swagger UI 파일들 복사
tasks.register('copyDocument', Copy) {
    dependsOn generateSwaggerUISample

    from file("build/swagger-ui-sample/")   // A
    into file("src/main/resources/static/docs") // A 위치의 파일을 여기에 복사
}

bootJar {
    dependsOn copyDocument   // 문서 작성 후에 .jar 생성
}

[Spring] restdocs + swagger 같이 사용하기 | Dev_ch 이 글의 설정을 바탕으로 내 프로젝트에 맞게 조금 변경하였다!! 이렇게 올려주셔서 감사합니다!!

기존 Rest Docs의 테스트 코드 변경

우선 document 메서드의 import 패키지를 import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;로 변경해야 한다.

// 기존 document
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;

// 변경된 document
import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;

Swagger@Tag@Operation(summary = "")를 사용하기 위해서는 resource(ResourceSnippetParameters.builder().build())를 사용하면 된다. 자세한 방법은 아래 코드를 통해 알아보면 좋겠다!

@Test
@DisplayName("즐겨찾기 추가")
@WithMockCustomUser
void addInfoBookmark() throws Exception {
    // when
    String chemId = "6646d897d7e4fb17f5ee2362";
    String bearerToken = "Bearer Json Web Token";

    ResultActions result = mockMvc.perform(
        RestDocumentationRequestBuilders.post("/api/save/bookmark/{chemId}", chemId)
        .header("Authorization", bearerToken)
    );

    doNothing().when(infoBookmarkService).saveInfoBookmark(chemId);

    // then
    result.andExpect(status().isOk())
        .andExpect(content().string("즐겨찾기 추가 성공"))
        .andDo(document("add-bookmark",
            // JSON 값 예쁘게 출력
            preprocessRequest(prettyPrint()),
            preprocessResponse(prettyPrint()),
            
            // Tag, Summary, Request, Response 명세
            resource(ResourceSnippetParameters.builder()
                .tag("Bookmark API")	// tag 지정
                .summary("즐겨찾기 추가")	// summary 지정
                // 요청 헤더 설명
                .requestHeaders(
                    headerWithName(HttpHeaders.AUTHORIZATION).attributes(key("type").value("String"))
                    .attributes(tokenFormat()).description("JWT (Your Token)")
                )
                // 요청 파라미터 설명
                .pathParameters(
                    parameterWithName("chemId").attributes(key("type").value("String")).description("즐겨찾기 하려는 분자의 ID(PK)")
                )
                .build()
            )
        ));
}

기존 Rest Docs에서는 document 메서드의 인자값으로 요청 및 응답에 대한 명세를 설정해줬다.

resource 메서드에 ResourceSnippetParameters를 인자값으로 넣어줄 때 tag, summary, 기존 requestHeaders, pathParameters, requestFields, responseFields 등을 빌더 패턴으로 변경해주면 Swagger에서 어노테이션으로 덕지덕지 붙인 설정을 테스트 코드로 만들 수 있다!!

tag메서드의 값이 같은 경우에는 각 API 명세가 하나의 그룹으로 이루어지게 된다. 아래 결과를 확인하면 바로 이해할 수 있을 것이다!

결과

gradlebuild를 하고 서버를 실행한 뒤, 도메인_주소/docs/index.html로 접속을 하면 아래와 같이 Swagger UI가 적용된 것을 확인할 수 있다!!

Bearer Token 설정

API 문서 상단쪽에 Authorize라는 버튼을 확인할 수 있다! 이건 Gradle에서 아래와 같이 설정했기 때문에 볼 수 있다.

tasks.withType(GenerateSwaggerUI) {
    dependsOn 'openapi3'
    
    doFirst {
        def swaggerUIFile = file("${openapi3.outputDirectory}/openapi3.yaml")

        def securitySchemesContent =  "  securitySchemes:\n" +  \
                                      "    bearerAuth:\n" +  \
                                      "      type: http\n" +  \
                                      "      scheme: bearer\n" +  \
                                      "      bearerFormat: JWT\n" +  \
                                      "      name: Authorization\n" +  \
                                      "      in: header\n" + \
                                      "      description: \"Use 'your-access-token' as the value of the Authorization header\"\n" + \
                                      "security:\n" +
                "  - bearerAuth: []  # Apply the security scheme here"

        swaggerUIFile.append securitySchemesContent
    }
}

간략히 설명하면 HTTP 요청을 보낼 때, Authorization 헤더에 Authorize 버튼을 클릭하고 입력한 값을 Bearer Token으로 지정하여 보낼 수 있게 설정한 것이다. 아래 사진과 함게 살펴보자.

위와 같이 로그인 후 발급받은 Access Token을 Authorize 버튼을 클릭한 뒤 입력란에 넣어주고 아래 Authorize 버튼은 다시 클릭하면 설정이 완료된다.

아래 Authorize 버튼은 다시 누르면 위와 같이 변경되게 된다. 보통 로그인 후 인증이 필요한 API를 요청할 때 이렇게 사용하면 된다. 만약 토큰을 사용하지 않을 거라면 Logout 버튼을 클릭하면 Authorization 헤더에 값을 명시하지 않고 요청을 보낸다.

잘 안보일 수 있겠지만, 빨간색 줄을 그은 부분이 Authorization 헤더다. 이 헤더의 값을 살펴보면 JWT로 인증을 할 때 토큰의 접두어로 붙여야 하는 "Bearer "값이 자동으로 붙어 있는 것을 확인할 수 있다. 이렇게 한번 설정해주면 계속 그 값이 유지되어 조금 더 편하게 API 요청을 보낼 수 있다!

소감

이 글과 같은 주제의 다른 글들 대부분은 Swagger를 설정하고 생성되는 openapi3.yaml 파일을 가지고 도커로 API 문서를 따로 배포하는 형태가 많았다. 물론 이 방식이 서버 실행 파일의 크기를 줄여주고 보안상 좋다는 것은 알고 있지만, 나는 로컬로 바로 확인하고 싶었다. 그래서 더 찾아보니 Swagger UI를 바로 적용할 수 있는 방법을 알려주는 글을 찾게 되었다! (참고에 명시한 글)

이전까지는 Swagger는 단점밖에 보이지 않아 사용할 생각이 없었지만, 이렇게 Spring Rest Docs와 함께 사용하는 방식으로 사용하니 앞으로는 이 방식으로만 사용할 것 같다 ㅋㅋㅋ 나는 이 방식이 굉장히 좋게 느껴졌고, 다른 개발자분들도 이런 생각을 하고 통합할 수 있게 만드셨다는 점에서 굉장히 대단하다고 느꼈다!


참고

[Spring] restdocs + swagger 같이 사용하기 | Dev_ch

profile
개발에 대한 기록과 복습을 위한 블로그 | Back-end Developer

0개의 댓글