Spring Boot 3.x.x Swagger API 명세서
Dependency 추가
Spring Boot 2.x대 에서는 SpringFox를 사용했다고 하는데 업데이트가 되지 않는게 오래 되어서 이제는 SpringDoc를 사용한다고 한다.
SpringDoc는 스프링부트 2점대와 3점대가 의존성을 추가하는 방법이 다르다고 하니 버전에 맞게 잘 사용하면 될 것 같다.
이번 프로젝트에서 사용한 버전은 3.3.3이므로 아래와 같이 의존성을 추가해줬다.
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
사용법
다른 블로그를 보면 환경변수를 설정하거나 Config 설정을 하는것으로 나와 있는데
나는 따로 설정을 하지 않고 기본적으로 설정되어 있는데로 사용하였다.
대신에 Spring Security를 사용하기 때문에 인증 없이 접속할 수 있도록 설정해주었다.
...
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
.requestMatchers("/", "/api/v1/auth/**", "oauth2/**").permitAll()
.requestMatchers("/v3/**", "/swagger-ui/**").permitAll()
.requestMatchers("/api/v1/admin/**").hasRole(UserRole.ADMIN.name())
.anyRequest().authenticated()
)
...
Swagger와 관련된 path를 모두 허용해준다.
@OpenAPIDefinition
API 명세서에 대한 설명을 설정할 수 있는 어노테이션이다.
@OpenAPIDefinition(info = @Info(title = "OAuth2.0 및 JWT 로그인 / 회원가입 API 명세서", description = "OAuth2.0, JWT 로그인 / 회원가입 학습하기 위한 API 명세서", version = "v1"))
public class Oauth2Application {
public static void main(String[] args) {
SpringApplication.run(Oauth2Application.class, args);
}
}
입력한 제목과 내용, 버전이 출력되는 것을 확인할 수 있다.
@Tag
도메인 별 API를 그룹화하여 설명할 수 있는 어노테이션이다.
@Tag(name = "JWT 로그인 / 회원가입 API 명세서", description = "JWT를 이용한 로그인 및 회원가입 API 명세입니다.")
대부분 컨트롤러를 도메인 별로 만들기 때문에 컨트롤러에 사용한다고 생각하면 될 것 같다.
@Operation
특정 API에 대한 설명할 수 있는 어노테이션이다.
@Operation(summary = "유저 아이디 중복 체크", description = "유저 아이디가 중복되었는지 확인하는 API")
특정 API에 대한 이름과 설명을 붙일 수 있다.
각각의 메서드에 붙여서 사용하면 된다.
@ApiResponses, @ApiResponse
ApiResponses는 ApiResponse를 하나로 묶어서 사용하는 어노테이션이다.
ApiResponse는 API에 대한 응답을 설명할 수 있는 어노테이션이다.
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Success", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "400", description = "Validation failed & Duplication Id", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "500", description = "Database error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
})
ApiResponse를 통해 각 응답 코드와 설명을 하면 된다.
ApiResponses는 여러 ApiResponse를 묶어 주는 역할인 것 같은데, 그냥 ApiResponses를 사용하지 않아도 되는 것 같다.
원래 Validation failed와 Duplication Id를 따로 분리했는데 같은 400번이라 먼저 나온 Validation failed만 나온다.
방법을 찾아봤는데 이렇게 하나로 설명을 묶는 것이 있고, 커스텀을 하는 방법이 있는 것 같은데 추후에 시도해봐야겠다.
전체 적용 코드
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/auth")
@Tag(name = "JWT 로그인 / 회원가입 API 명세서", description = "JWT를 이용한 로그인 및 회원가입 API 명세입니다.")
public class AuthController {
private final AuthService authService;
@Operation(summary = "유저 아이디 중복 체크", description = "유저 아이디가 중복되었는지 확인하는 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Success", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "400", description = "Validation failed & Duplication Id", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "500", description = "Database error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
})
@PostMapping("/id-check")
public ResponseEntity<CertificationResponseDto> idCheck(
@RequestBody @Valid IdCheckRequestDto requestDto
) {
authService.idCheck(requestDto);
return CertificationResponseDto.success();
}
@Operation(summary = "이메일 인증", description = "이메일로 인증 코드를 보내는 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Success", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "400", description = "Validation failed & Duplication Id", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "500", description = "Mail Send failed & Database error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
})
@PostMapping("/email-certification")
public ResponseEntity<CertificationResponseDto> mailCertification(
@RequestBody @Valid MailCertificationRequestDto requestDto
) {
authService.mailCertification(requestDto);
return CertificationResponseDto.success();
}
@Operation(summary = "이메일 인증 코드 체크", description = "이메일로 온 인증 코드를 확인하는 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Success", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "400", description = "Validation failed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "401", description = "Certification failed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "500", description = "Database error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
})
@PostMapping("/check-certification")
public ResponseEntity<CertificationResponseDto> checkCertification(
@RequestBody @Valid CheckCertificationRequestDto requestDto
) {
authService.checkCertification(requestDto);
return CertificationResponseDto.success();
}
@Operation(summary = "회원가입", description = "유저 회원가입 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Success", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "400", description = "Validation failed & Duplication Id", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "401", description = "Certification failed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "500", description = "Database error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
})
@PostMapping("/signup")
public ResponseEntity<CertificationResponseDto> signup(
@RequestBody @Valid SignUpRequestDto requestDto
) {
authService.signup(requestDto);
return CertificationResponseDto.success();
}
@Operation(summary = "로그인", description = "유저 로그인 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Success", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SignInResponseDto.class))),
@ApiResponse(responseCode = "400", description = "Validation failed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "401", description = "Login information mismatch", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
@ApiResponse(responseCode = "500", description = "Database error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CertificationResponseDto.class))),
})
@PostMapping("/signin")
public ResponseEntity<SignInResponseDto> signin(
@RequestBody @Valid SignInRequestDto requestDto
) {
String token = authService.signin(requestDto);
return SignInResponseDto.success(token);
}
}
마무리
위의 전체 적용 코드를 보면 컨트롤러가 Swagger 관련 어노테이션 때문에 지저분해 보이고 가독성이 떨어지는 것 같다.
어노테이션을 커스텀하는 방법이 있는 것 같으니 추후에 그 방법에 대해서 공부해봐야겠다.