diff --git a/api/api-game/src/main/java/com/pgms/apigame/dto/PageCondition.java b/api/api-game/src/main/java/com/pgms/apigame/dto/PageCondition.java index 96c64ca..a371041 100644 --- a/api/api-game/src/main/java/com/pgms/apigame/dto/PageCondition.java +++ b/api/api-game/src/main/java/com/pgms/apigame/dto/PageCondition.java @@ -3,6 +3,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import io.swagger.v3.oas.annotations.Hidden; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import lombok.AccessLevel; @@ -28,6 +29,7 @@ public PageCondition(Integer page, Integer size) { this.size = Boolean.TRUE.equals(isValidSize(size)) ? size : DEFAULT_SIZE; } + @Hidden public Pageable getPageable() { return PageRequest.of(this.page - 1, this.size); } diff --git a/api/api-member/src/main/java/com/pgms/apimember/annotation/SwaggerResponseAuth.java b/api/api-member/src/main/java/com/pgms/apimember/annotation/SwaggerResponseAuth.java new file mode 100644 index 0000000..4203253 --- /dev/null +++ b/api/api-member/src/main/java/com/pgms/apimember/annotation/SwaggerResponseAuth.java @@ -0,0 +1,25 @@ +package com.pgms.apimember.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Operation() +@ApiResponses({ + @ApiResponse(responseCode = "200", description = "성공"), + @ApiResponse(responseCode = "sec-401/01", description = "로그인이 필요합니다.", content = @Content), + @ApiResponse(responseCode = "sec-401/02", description = "액세스 토큰이 만료되었으니 다시 로그인 해주세요.", content = @Content), + @ApiResponse(responseCode = "sec-401/03", description = "리프레시 토큰이 만료되었으니 다시 로그인 해주세요.", content = @Content), + @ApiResponse(responseCode = "sec-401/04", description = "현재 토큰은 로그아웃 상태로 재로그인이 필요합니다.", content = @Content), + @ApiResponse(responseCode = "sec-403/01", description = "해당 토큰에 접근 권한이 없습니다.", content = @Content) +}) +public @interface SwaggerResponseAuth { +} diff --git a/api/api-member/src/main/java/com/pgms/apimember/controller/AuthController.java b/api/api-member/src/main/java/com/pgms/apimember/controller/AuthController.java index b0352c1..215dc13 100644 --- a/api/api-member/src/main/java/com/pgms/apimember/controller/AuthController.java +++ b/api/api-member/src/main/java/com/pgms/apimember/controller/AuthController.java @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.pgms.apimember.annotation.SwaggerResponseAuth; import com.pgms.apimember.dto.request.LoginRequest; import com.pgms.apimember.dto.response.AuthResponse; import com.pgms.apimember.service.AuthService; @@ -17,9 +18,13 @@ import com.pgms.coresecurity.jwt.JwtTokenProvider; import com.pgms.coresecurity.resolver.CurrentAccount; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +@Tag(name = "회원 인증", description = "회원 인증 관련 API 입니다.") +@SwaggerResponseAuth @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/auth") @@ -28,11 +33,13 @@ public class AuthController { private final AuthService authService; private final JwtTokenProvider jwtTokenProvider; + @Operation(summary = "로그인") @PostMapping("/login") public ResponseEntity> login(@Valid @RequestBody LoginRequest request) { return ResponseEntity.ok(ApiResponse.of(authService.login(request))); } + @Operation(summary = "로그아웃") @PostMapping("/logout") public ResponseEntity> logout( @RequestHeader("Authorization") String bearerToken, @@ -42,11 +49,13 @@ public ResponseEntity> logout( return ResponseEntity.ok(ApiResponse.of(SUCCESS)); } + @Operation(summary = "게스트 로그인") @PostMapping("/guest") public ResponseEntity> guestLogin() { return ResponseEntity.ok(ApiResponse.of(authService.guestLogin())); } + @Operation(summary = "토큰 재발급") @PostMapping("/reissue") public ResponseEntity> reIssueAccessToken( @CookieValue("refreshToken") String refreshToken) { diff --git a/build.gradle b/build.gradle index 713ae34..31befd5 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,9 @@ subprojects { // lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + + // swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' } test { diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/config/SwaggerConfig.java b/core/core-domain/src/main/java/com/pgms/coredomain/config/SwaggerConfig.java new file mode 100644 index 0000000..5876858 --- /dev/null +++ b/core/core-domain/src/main/java/com/pgms/coredomain/config/SwaggerConfig.java @@ -0,0 +1,36 @@ +package com.pgms.coredomain.config; + +import java.util.Arrays; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.servers.Server; +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; + +@OpenAPIDefinition( + info = @Info(title = "티키타자 백엔드 API 명세서", + description = "티키타자 백엔드 API 명세서 입니다", + version = "0.1"), + servers = { + @Server(url = "/", description = "티키타자 서버 URL")} +) +@Configuration +public class SwaggerConfig { + @Bean + public OpenAPI openAPI() { + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT") + .in(SecurityScheme.In.HEADER).name("Authorization"); + SecurityRequirement securityRequirement = new SecurityRequirement().addList("bearerAuth"); + + return new OpenAPI() + .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) + .security(Arrays.asList(securityRequirement)); + } +} diff --git a/core/core-security/src/main/java/com/pgms/coresecurity/config/WebSecurityConfig.java b/core/core-security/src/main/java/com/pgms/coresecurity/config/WebSecurityConfig.java index a0e8b98..c5639de 100644 --- a/core/core-security/src/main/java/com/pgms/coresecurity/config/WebSecurityConfig.java +++ b/core/core-security/src/main/java/com/pgms/coresecurity/config/WebSecurityConfig.java @@ -144,6 +144,8 @@ private RequestMatcher[] requestHasRoleUser() { private RequestMatcher[] requestPermitAll() { List requestMatchers = List.of( antMatcher("/"), + antMatcher("/swagger-ui/**"), + antMatcher("/v3/api-docs/**"), antMatcher("/ws/**"), antMatcher("/from/**"), antMatcher("/to/**"), diff --git a/core/core-security/src/main/java/com/pgms/coresecurity/resolver/CurrentAccount.java b/core/core-security/src/main/java/com/pgms/coresecurity/resolver/CurrentAccount.java index b60b712..c8dcfaa 100644 --- a/core/core-security/src/main/java/com/pgms/coresecurity/resolver/CurrentAccount.java +++ b/core/core-security/src/main/java/com/pgms/coresecurity/resolver/CurrentAccount.java @@ -5,6 +5,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import io.swagger.v3.oas.annotations.Parameter; + +@Parameter(hidden = true) @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface CurrentAccount { diff --git a/http/test.http b/http/test.http index bc4aea8..53ad6c5 100644 --- a/http/test.http +++ b/http/test.http @@ -70,7 +70,8 @@ Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpZCI6MSwic3ViIjoidGVzdEBuYXZlci5jb { "title": "빙봉의 게임", "maxPlayer": 2, - "roundCount": 5 + "roundCount": 5, + "gameType": "SENTENCE" } ### 조회