Web/SrpingBoot

[Swagger & SpringBoot 3.x] 스프링부트 3.x 스웨거UI 적용- SpringDocs

나는나는용 2025. 4. 9. 22:31
728x90

Trouble

스프링부트 2.x대에서 사용하던 Springfox 라이브러리가 메인테이너되지 않는 이슈...

 

진짜 별의별 레퍼런스를 찾아봐도 내 기존 Springfox콜랍 코드를 낫게해줄 처방전은 없었다ㅠ

 

Solution

스프링부트 3.x에서 Swagger를 적용하기 위해서는 SpringDocs 라이브러리를 활용해야함.

 

과정이 궁금한 사람들도 있을테지만, 이 블로그는 나의 동의보감 이니까 결론적인것만 담아보겠다.ㅎ

 

Result(결론적인 해결과정)

Step 1 : build.gradle

많고 많은 의존성들이 있지만,

내 개발 환경과, Swagger를 위한 의존성만 남겨보자면,

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.7'
}

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

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {

    // Swagger
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

}

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

 

코끼리 빙글뱅글 돌려주시고,

 

Step 2 : SwaggerConfig

@Configuration 어노테이션으로 설정파일임을 알리고,

@Bean 빈 등록 해주기.

package com.example.backend.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI()
                .components(new Components())
                .info(apiInfo());
    }

    private Info apiInfo() {
        return new Info()
                .title("SpringBoot Swagger")
                .description("유저, 결제, 토큰 관리 및 fastAPI 연결")
                .version("1.0.0");
    }

}

 

apiInfo 메서드는, 사진 보면 감이 올텐데,

'제목, 설명, 버전'같이, API 정보를 설정하는 부분이다.

 

( Step 3 : WebSecurityConfig )

SpringSecurity 를 사용하고있으므로, permitAll에 swagger관련된 부분도 추가해주자.

 

Step 4 : Docs, 꾸미기💄

Controller, DTO의 Docs를 예쁘게 작성하는 방법은 여러가지가 있는데, 예시를 작성해두겠다.

아마 이 예시 긁어다가 우리 모두의 친구 GPT에게 "이거 참고해서 내 컨트롤러의 Docs를 작성해줘"라고 부탁하면,

 

기깔난 XxxControllerDocs 인터페이스를 작성해줄것이다.

ex 1. Controller

// 패키지 구조를 어찌 잡았나 궁금한 사람들을 위해~
// package com.example.backend.swagger;

@Tag(name = "User", description = "회원 관련 API")
public interface UserControllerDocs {

    @Operation(
            summary = "회원가입",
            description = "사용자가 회원가입을 진행합니다.",
            requestBody = @RequestBody(
                    required = true,
                    content = @Content(schema = @Schema(implementation = SignUpRequestDTO.class))
            ),
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "회원가입 성공",
                            content = @Content(schema = @Schema(implementation = UrlResponseDTO.class))
                    ),
                    @ApiResponse(responseCode = "400", description = "요청 형식 오류"),
                    @ApiResponse(responseCode = "500", description = "서버 에러")
            }
    )
    ResponseEntity<UrlResponseDTO> signup(@RequestBody SignUpRequestDTO signUpRequestDTO);
    
    
    @Operation(
            summary = "튜토리얼 상태 업데이트",
            description = "인증된 사용자의 튜토리얼 상태를 업데이트합니다. (토큰 기반 인증 필요)",
            responses = {
                    @ApiResponse(responseCode = "200", description = "튜토리얼 상태가 업데이트되었습니다.",
                            content = @Content(schema = @Schema(implementation = String.class))),
                    @ApiResponse(responseCode = "401", description = "인증 정보가 없습니다.",
                            content = @Content(schema = @Schema(implementation = String.class))),
                    @ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없습니다.",
                            content = @Content(schema = @Schema(implementation = String.class))),
                    @ApiResponse(responseCode = "409", description = "튜토리얼 상태 업데이트 중 충돌 발생",
                            content = @Content(schema = @Schema(implementation = String.class))),
                    @ApiResponse(responseCode = "500", description = "서버 에러가 발생하였습니다.",
                            content = @Content(schema = @Schema(implementation = String.class)))
            }
    )
    ResponseEntity<?> updateTutorialStatus(@AuthenticationPrincipal SecurityUserDto authenticatedUser);
    
    // 더 있지만, 일단 얘만 남겨둘래용ㅎ
}

 

이걸 작성해두고, 


다음과 같이, 컨트롤러 가셔서 implements 해주세요~

public class UserController implements UserControllerDocs {

    private final UserService userService;

    @PostMapping("/signup")
    public ResponseEntity<UrlResponseDTO> signup(@RequestBody SignUpRequestDTO signUpRequestDTO) {

        userService.saveUser(signUpRequestDTO); // 회원가입 진행 (DB 저장)

        return ResponseEntity.ok(
                UrlResponseDTO.builder()
                        .message("회원가입을 성공했습니다.")
                        .build()
        );
    }
    
    @PostMapping("/tutorial")
    public ResponseEntity<?> updateTutorialStatus(@AuthenticationPrincipal SecurityUserDto authenticatedUser) {
        // 인증 정보 확인
        if (authenticatedUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("인증 정보가 없습니다.");
        }

        try {
            // 튜토리얼 상태 업데이트 로직 실행
            userService.tutorialComplete(authenticatedUser);
            return ResponseEntity.ok("튜토리얼 상태가 업데이트되었습니다.");
        } catch (UsernameNotFoundException e) {
            // 예: 사용자 정보가 DB에 없을 경우
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("사용자를 찾을 수 없습니다.");
        } catch (IllegalStateException e) {
            // 예: 이미 튜토리얼이 완료된 상태라면
            return ResponseEntity.status(HttpStatus.CONFLICT).body("튜토리얼 상태 업데이트 중 충돌 발생: " + e.getMessage());
        } catch (Exception e) {
            // 그 외의 예상치 못한 오류 처리
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("서버 에러가 발생하였습니다: " + e.getMessage());
        }
    }
}

 

ex 2. DTO

 

이건 Docs 만들어서 할 필요 없이 Schema 어노테이션 사용하셔요~

@Getter
@Setter
@Schema(description = "회원가입 요청 DTO")
public class SignUpRequestDTO {
    @Schema(description = "사용자 이메일", example = "user@example.com")
    private String email;

    @Schema(description = "비밀번호", example = "securePassword123")
    private String password;

    @Schema(description = "비밀번호 확인", example = "securePassword123")
    private String confirmPassword;

    @Schema(description = "이름", example = "홍길동")
    private String name;

    @Schema(description = "나이", example = "28")
    private int age;

    @Schema(description = "전화번호", example = "01012345678")
    private String phone;

    @Schema(description = "회원 구분 (APPLICANT or HR)", example = "APPLICANT")
    private String role;

    @Schema(description = "회사명 (HR 전용)", example = "ABC Corp")
    private String companyName;

    @Schema(description = "사업자등록번호 (HR 전용)", example = "123-45-67890")
    private String businessNumber;
}

 

접속, 확인

// 예시) http://localhost:8080/swagger-ui/index.html
http://서버주소:포트번호/swagger-ui/index.html

 

이런식으로 주소창에 입력해서 확인 가능하다.

 

 

 

적용 결과 예시

로컬이라면, 인텔리제이(사용하시는 IDE) 실행시키고,

 

위의 URL 접속해서,

궁금한 엔드포인트 펼쳐보면 짜자잔~

 

다음 사진에서 Request body에 Example Value 옆의 Schema를 눌러보셔도 되지만,

 

아래 쭉 내려보면 Schemas 모아둔곳도 있답니다!

728x90