diff --git a/config b/config index 6e61b815..8cac261b 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 6e61b8157725b24821dff68581c6a45dadad98d1 +Subproject commit 8cac261b72556ebd4213d2d0590de18bf61e285d diff --git a/src/main/java/starlight/adapter/aireport/webapi/AiReportController.java b/src/main/java/starlight/adapter/aireport/webapi/AiReportController.java index 7ff19d4e..e39411be 100644 --- a/src/main/java/starlight/adapter/aireport/webapi/AiReportController.java +++ b/src/main/java/starlight/adapter/aireport/webapi/AiReportController.java @@ -1,6 +1,7 @@ package starlight.adapter.aireport.webapi; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -18,6 +19,7 @@ @RequiredArgsConstructor @RequestMapping("/v1/ai-reports") @Tag(name = "AI 리포트", description = "AI 리포트 채점 및 조회 API") +@SecurityRequirement(name = "bearerAuth") public class AiReportController { private final AiReportService aiReportService; diff --git a/src/main/java/starlight/adapter/aireport/webapi/swagger/ImageApiDoc.java b/src/main/java/starlight/adapter/aireport/webapi/swagger/ImageApiDoc.java index a9bc0b1b..c1e32213 100644 --- a/src/main/java/starlight/adapter/aireport/webapi/swagger/ImageApiDoc.java +++ b/src/main/java/starlight/adapter/aireport/webapi/swagger/ImageApiDoc.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.MediaType; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -20,7 +21,8 @@ public interface ImageApiDoc { @Operation( summary = "Presigned URL 발급", - description = "S3 Presigned URL을 발급합니다." + description = "S3 Presigned URL을 발급합니다.", + security = @SecurityRequirement(name = "bearerAuth") ) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -52,7 +54,8 @@ ApiResponse getPresignedUrl( @Operation( summary = "이미지 공개 전환", - description = "업로드된 이미지를 공개 상태로 전환합니다." + description = "업로드된 이미지를 공개 상태로 전환합니다.", + security = @SecurityRequirement(name = "bearerAuth") ) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( diff --git a/src/main/java/starlight/adapter/businessplan/webapi/BusinessPlanController.java b/src/main/java/starlight/adapter/businessplan/webapi/BusinessPlanController.java index d66732cd..50affe2c 100644 --- a/src/main/java/starlight/adapter/businessplan/webapi/BusinessPlanController.java +++ b/src/main/java/starlight/adapter/businessplan/webapi/BusinessPlanController.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; @@ -29,6 +30,7 @@ @RequiredArgsConstructor @RequestMapping("/v1/business-plans") @Tag(name = "사업계획서", description = "사업계획서 API") +@SecurityRequirement(name = "bearerAuth") public class BusinessPlanController { private final BusinessPlanService businessPlanService; diff --git a/src/main/java/starlight/adapter/expert/webapi/swagger/ExpertApiDoc.java b/src/main/java/starlight/adapter/expert/webapi/swagger/ExpertApiDoc.java index 7c748d6f..fa0669d9 100644 --- a/src/main/java/starlight/adapter/expert/webapi/swagger/ExpertApiDoc.java +++ b/src/main/java/starlight/adapter/expert/webapi/swagger/ExpertApiDoc.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; @@ -23,7 +24,8 @@ public interface ExpertApiDoc { @Operation( summary = "전문가 목록 조회", - description = "전체 전문가 목록을 반환합니다." + description = "전체 전문가 목록을 반환합니다.", + security = {} ) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -114,7 +116,7 @@ public interface ExpertApiDoc { @GetMapping ApiResponse> search(); - @Operation(summary = "전문가 상세 조회") + @Operation(summary = "전문가 상세 조회", security = {}) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "200", @@ -186,7 +188,8 @@ ApiResponse detail( @Operation( summary = "전문가 상세 내 AI 리포트 보유 사업계획서 목록", - description = "지정된 전문가의 전문가 상세 페이지에서 로그인한 사용자의 사업계획서 중 AI 리포트가 생성된 항목만 조회합니다." + description = "지정된 전문가의 전문가 상세 페이지에서 로그인한 사용자의 사업계획서 중 AI 리포트가 생성된 항목만 조회합니다.", + security = @SecurityRequirement(name = "bearerAuth") ) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( diff --git a/src/main/java/starlight/adapter/expertApplication/webapi/swagger/ExpertApplicationApiDoc.java b/src/main/java/starlight/adapter/expertApplication/webapi/swagger/ExpertApplicationApiDoc.java index a492d420..8327e117 100644 --- a/src/main/java/starlight/adapter/expertApplication/webapi/swagger/ExpertApplicationApiDoc.java +++ b/src/main/java/starlight/adapter/expertApplication/webapi/swagger/ExpertApplicationApiDoc.java @@ -28,7 +28,7 @@ public interface ExpertApplicationApiDoc { - 동일한 전문가에게 동일한 사업계획서로 중복 요청할 수 없습니다. - 이메일 발송은 비동기로 처리되며, 요청 즉시 응답을 반환합니다. """, - security = @SecurityRequirement(name = "Bearer Authentication") + security = @SecurityRequirement(name = "bearerAuth") ) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( diff --git a/src/main/java/starlight/adapter/member/auth/redis/RedisKeyValueMap.java b/src/main/java/starlight/adapter/member/auth/redis/RedisKeyValueMap.java index 1f59992b..5b064864 100644 --- a/src/main/java/starlight/adapter/member/auth/redis/RedisKeyValueMap.java +++ b/src/main/java/starlight/adapter/member/auth/redis/RedisKeyValueMap.java @@ -41,10 +41,8 @@ public void setValue(String key, String value, Long timeout) { public String getValue(String key) { try { ValueOperations values = redisTemplate.opsForValue(); - if (values.get(key) == null) { - return ""; - } - return values.get(key).toString(); + Object value = values.get(key); + return value == null ? null : value.toString(); } catch (Exception e) { throw new GlobalException(GlobalErrorType.REDIS_GET_ERROR); } diff --git a/src/main/java/starlight/adapter/member/auth/security/filter/JwtFilter.java b/src/main/java/starlight/adapter/member/auth/security/filter/JwtFilter.java index f22c4c2f..4992e5bd 100644 --- a/src/main/java/starlight/adapter/member/auth/security/filter/JwtFilter.java +++ b/src/main/java/starlight/adapter/member/auth/security/filter/JwtFilter.java @@ -34,8 +34,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = tokenResolver.resolveAccessToken(request); + boolean hasToken = StringUtils.hasText(token); + String redisValue = hasToken ? redisClient.getValue(token) : null; + boolean isBlacklisted = hasToken && redisValue != null; + boolean isValid = hasToken && tokenProvider.validateToken(token); - if (StringUtils.hasText(token) && redisClient.getValue(token) == null && tokenProvider.validateToken(token)) { + if (hasToken && !isBlacklisted && isValid) { String email = tokenProvider.getEmail(token); UserDetails userDetails = authDetailsService.loadUserByUsername(email); diff --git a/src/main/java/starlight/adapter/member/auth/security/jwt/JwtTokenProvider.java b/src/main/java/starlight/adapter/member/auth/security/jwt/JwtTokenProvider.java index cd40e104..4928d1d3 100644 --- a/src/main/java/starlight/adapter/member/auth/security/jwt/JwtTokenProvider.java +++ b/src/main/java/starlight/adapter/member/auth/security/jwt/JwtTokenProvider.java @@ -152,7 +152,9 @@ public String getEmail(String token) { */ @Override public Long getExpirationTime(String token) { - return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getExpiration().getTime(); + return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody() + .getExpiration() + .getTime(); } /** diff --git a/src/main/java/starlight/adapter/member/auth/webapi/swagger/AuthApiDoc.java b/src/main/java/starlight/adapter/member/auth/webapi/swagger/AuthApiDoc.java index 02db8b5a..c652a005 100644 --- a/src/main/java/starlight/adapter/member/auth/webapi/swagger/AuthApiDoc.java +++ b/src/main/java/starlight/adapter/member/auth/webapi/swagger/AuthApiDoc.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -182,7 +183,8 @@ ApiResponse signIn( @Operation( summary = "로그아웃", - description = "사용자 로그아웃 기능" + description = "사용자 로그아웃 기능", + security = @SecurityRequirement(name = "bearerAuth") ) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( @@ -248,7 +250,8 @@ ApiResponse signIn( @Operation( summary = "토큰 재발급", - description = "AccessToken 만료 시 RefreshToken으로 AccessToken 재발급" + description = "AccessToken 만료 시 RefreshToken으로 AccessToken 재발급", + security = @SecurityRequirement(name = "bearerAuth") ) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( diff --git a/src/main/java/starlight/adapter/member/webapi/swagger/MemberApiDoc.java b/src/main/java/starlight/adapter/member/webapi/swagger/MemberApiDoc.java index 9295dbf5..98f04eb4 100644 --- a/src/main/java/starlight/adapter/member/webapi/swagger/MemberApiDoc.java +++ b/src/main/java/starlight/adapter/member/webapi/swagger/MemberApiDoc.java @@ -16,7 +16,7 @@ @Tag(name = "사용자", description = "사용자 관련 API") public interface MemberApiDoc { - @Operation(summary = "멤버 정보를 조회합니다.", security = @SecurityRequirement(name = "Bearer Authentication")) + @Operation(summary = "멤버 정보를 조회합니다.", security = @SecurityRequirement(name = "bearerAuth")) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "200", diff --git a/src/main/java/starlight/adapter/order/webapi/swagger/OrderApiDoc.java b/src/main/java/starlight/adapter/order/webapi/swagger/OrderApiDoc.java index 8c406bc7..a59b7df1 100644 --- a/src/main/java/starlight/adapter/order/webapi/swagger/OrderApiDoc.java +++ b/src/main/java/starlight/adapter/order/webapi/swagger/OrderApiDoc.java @@ -28,7 +28,7 @@ @Tag(name = "결제", description = "결제 관련 API") public interface OrderApiDoc { - @Operation(summary = "결제 준비", security = @SecurityRequirement(name = "Bearer Authentication")) + @Operation(summary = "결제 준비", security = @SecurityRequirement(name = "bearerAuth")) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "200", @@ -120,7 +120,7 @@ ApiResponse prepareOrder( @AuthenticationPrincipal AuthenticatedMember authenticatedMember ); - @Operation(summary = "결제 승인", security = @SecurityRequirement(name = "Bearer Authentication")) + @Operation(summary = "결제 승인", security = @SecurityRequirement(name = "bearerAuth")) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "200", @@ -315,7 +315,7 @@ ApiResponse cancelPayment( @Valid @RequestBody OrderCancelRequest request ); - @Operation(summary = "내 결제 내역 조회", security = @SecurityRequirement(name = "Bearer Authentication")) + @Operation(summary = "내 결제 내역 조회", security = @SecurityRequirement(name = "bearerAuth")) @ApiResponses({ @io.swagger.v3.oas.annotations.responses.ApiResponse( responseCode = "200", diff --git a/src/main/java/starlight/bootstrap/SwaggerConfig.java b/src/main/java/starlight/bootstrap/SwaggerConfig.java index 01551e76..df6ebb17 100644 --- a/src/main/java/starlight/bootstrap/SwaggerConfig.java +++ b/src/main/java/starlight/bootstrap/SwaggerConfig.java @@ -2,10 +2,10 @@ import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; 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; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -16,7 +16,7 @@ info = @Info(title = "StarLight 명세서", description = "StarLight API 명세서", version = "v1" ), servers = { - @Server(url = "${cors.origin.server}",description = "서버 URL") + @Server(url = "${cors.origin.server}", description = "서버 URL") } ) @@ -26,9 +26,13 @@ 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"); + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER) + .name("Authorization"); + io.swagger.v3.oas.models.security.SecurityRequirement securityRequirement = + new io.swagger.v3.oas.models.security.SecurityRequirement().addList("bearerAuth"); return new OpenAPI() .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) diff --git a/src/test/java/starlight/adapter/member/auth/redis/RedisKeyValueMapTest.java b/src/test/java/starlight/adapter/member/auth/redis/RedisKeyValueMapTest.java index 8d35d8f0..dcb242a2 100644 --- a/src/test/java/starlight/adapter/member/auth/redis/RedisKeyValueMapTest.java +++ b/src/test/java/starlight/adapter/member/auth/redis/RedisKeyValueMapTest.java @@ -81,7 +81,7 @@ void getValue_Success() { @Test @DisplayName("getValue 실패 - 값이 없는 경우") - void getValue_ReturnEmptyString() { + void getValue_ReturnNull() { // given String key = "testKey"; given(redisTemplate.opsForValue()).willReturn(valueOperations); @@ -91,7 +91,7 @@ void getValue_ReturnEmptyString() { String result = redisKeyValueMap.getValue(key); // then - assertThat(result).isEmpty(); + assertThat(result).isNull(); } @Test