From 747a33faed14e7fd29c2f81990c9647664018bc2 Mon Sep 17 00:00:00 2001 From: Hong0329 Date: Tue, 16 Jan 2024 06:08:30 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[FIX]=EC=95=A1=EC=85=98=20=ED=94=8C?= =?UTF-8?q?=EB=9E=9C=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=EA=B0=80=20=EB=8B=AC=EB=A6=B0=20=EC=95=A1=EC=85=98=ED=94=8C?= =?UTF-8?q?=EB=9E=9C=EC=9D=B8=EC=A7=80=20=ED=8F=AC=ED=95=A8=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actionplan/dto/response/ActionPlanGetResponseDto.java | 2 ++ .../dto/response/DoingActionPlanGetResponseDto.java | 2 ++ .../dto/response/FinishedActionPlanGetResponseDto.java | 2 ++ .../actionplan/service/Impl/ActionPlanServiceImpl.java | 8 +++++--- .../api/review/repository/ReviewRepository.java | 2 ++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java index 0026d9d..f34ccf1 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java @@ -17,4 +17,6 @@ public class ActionPlanGetResponseDto { private Boolean isScraped; private Boolean isFinished; + + private Boolean isReviewed; } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java index 15654d8..6bdb694 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java @@ -16,4 +16,6 @@ public class DoingActionPlanGetResponseDto { private Boolean isScraped; private Long seedId; + + private Boolean isReviewed; } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java index 8a2b0f2..a044521 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java @@ -16,4 +16,6 @@ public class FinishedActionPlanGetResponseDto { private Boolean isScraped; private Long seedId; + + private Boolean isReviewed; } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/Impl/ActionPlanServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/Impl/ActionPlanServiceImpl.java index f1c540e..67535b4 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/Impl/ActionPlanServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/Impl/ActionPlanServiceImpl.java @@ -9,6 +9,7 @@ import com.example.growthookserver.api.actionplan.repository.ActionPlanRepository; import com.example.growthookserver.api.actionplan.service.ActionPlanService; import com.example.growthookserver.api.member.domain.Member; +import com.example.growthookserver.api.review.repository.ReviewRepository; import com.example.growthookserver.api.seed.domain.Seed; import com.example.growthookserver.api.seed.repository.SeedRepository; import com.example.growthookserver.common.exception.BadRequestException; @@ -29,6 +30,7 @@ public class ActionPlanServiceImpl implements ActionPlanService { private final ActionPlanRepository actionPlanRepository; private final SeedRepository seedRepository; + private final ReviewRepository reviewRepository; @Override @Transactional @@ -47,7 +49,7 @@ public List getActionPlan(Long seedId) { List actionPlans = actionPlanRepository.findAllBySeedId(seedId); return actionPlans.stream() - .map(actionPlan -> ActionPlanGetResponseDto.of(actionPlan.getId(), actionPlan.getContent(), actionPlan.getIsScraped(), actionPlan.getIsFinished())) + .map(actionPlan -> ActionPlanGetResponseDto.of(actionPlan.getId(), actionPlan.getContent(), actionPlan.getIsScraped(), actionPlan.getIsFinished(),reviewRepository.existsByActionPlan(actionPlan))) .collect(Collectors.toList()); } @@ -98,7 +100,7 @@ public List getDoingActionPlan(Long memberId) { List doingActionPlans = actionPlanRepository.findAllBySeedCaveMemberIdAndIsFinished(memberId,false); return doingActionPlans.stream() - .map(actionPlan -> DoingActionPlanGetResponseDto.of(actionPlan.getId(), actionPlan.getContent(), actionPlan.getIsScraped(),actionPlan.getSeed().getId())) + .map(actionPlan -> DoingActionPlanGetResponseDto.of(actionPlan.getId(), actionPlan.getContent(), actionPlan.getIsScraped(),actionPlan.getSeed().getId(),reviewRepository.existsByActionPlan(actionPlan))) .collect(Collectors.toList()); } @@ -107,7 +109,7 @@ public List getFinishedActionPlan(Long memberI List finishedActionPlans = actionPlanRepository.findAllBySeedCaveMemberIdAndIsFinished(memberId,true); return finishedActionPlans.stream() - .map(actionPlan -> FinishedActionPlanGetResponseDto.of(actionPlan.getId(), actionPlan.getContent(), actionPlan.getIsScraped(),actionPlan.getSeed().getId())) + .map(actionPlan -> FinishedActionPlanGetResponseDto.of(actionPlan.getId(), actionPlan.getContent(), actionPlan.getIsScraped(),actionPlan.getSeed().getId(),reviewRepository.existsByActionPlan(actionPlan))) .collect(Collectors.toList()); } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/review/repository/ReviewRepository.java b/growthookServer/src/main/java/com/example/growthookserver/api/review/repository/ReviewRepository.java index 704761f..ed5f7a1 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/review/repository/ReviewRepository.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/review/repository/ReviewRepository.java @@ -1,5 +1,6 @@ package com.example.growthookserver.api.review.repository; +import com.example.growthookserver.api.actionplan.domain.ActionPlan; import com.example.growthookserver.api.cave.domain.Cave; import com.example.growthookserver.api.review.domain.Review; import com.example.growthookserver.common.exception.NotFoundException; @@ -15,5 +16,6 @@ default Review findReviewByActionPlanIdOrThrow(Long actionPlanId) { return findReviewByActionPlanId(actionPlanId) .orElseThrow(() -> new NotFoundException(ErrorStatus.NOT_FOUND_REVIEW.getMessage())); } + boolean existsByActionPlan(ActionPlan actionPlan); } From 5b79ccea7e1f37eabf26d24bb6e3019d0a9d3684 Mon Sep 17 00:00:00 2001 From: Hong0329 Date: Tue, 16 Jan 2024 06:28:55 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[FEAT]=EC=95=A1=EC=85=98=20=ED=94=8C?= =?UTF-8?q?=EB=9E=9C=20=EC=8A=A4=ED=81=AC=EB=9E=A9=20=ED=86=A0=EA=B8=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/actionplan/controller/ActionPlanController.java | 8 ++++++++ .../growthookserver/api/actionplan/domain/ActionPlan.java | 4 ++++ .../api/actionplan/service/ActionPlanService.java | 2 ++ .../actionplan/service/Impl/ActionPlanServiceImpl.java | 7 +++++++ .../growthookserver/common/response/SuccessStatus.java | 1 + 5 files changed, 22 insertions(+) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java index b24130f..e51a494 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java @@ -82,4 +82,12 @@ public ApiResponse getDoingActionPlan(@PathVariab public ApiResponse getFinishedActionPlan(@PathVariable Long memberId) { return ApiResponse.success(SuccessStatus.GET_FINISHED_ACTIONPLAN_SUCCESS, actionPlanService.getFinishedActionPlan(memberId)); } + + @PatchMapping("actionplan/{actionplanId}/scrap") + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "ScrapedActionPlan", description = "액션 플랜을 스크랩 하는 API입니다.") + public ApiResponse toggleActionPlanScrapStatus(@PathVariable Long actionplanId) { + actionPlanService.toggleActionPlanScrapStatus(actionplanId); + return ApiResponse.success(SuccessStatus.TOGGLE_ACTIONPLAN_SCRAP.getStatusCode(), SuccessStatus.TOGGLE_SEED_SCRAP_STATUS.getMessage()); + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/domain/ActionPlan.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/domain/ActionPlan.java index 6300441..b25eb22 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/domain/ActionPlan.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/domain/ActionPlan.java @@ -52,4 +52,8 @@ public void updateActionPlan(String newContent) { public void completeActionPlan(Boolean newIsFinished) { this.isFinished = newIsFinished; } + + public void toggleScrapStatus() { + this.isScraped = !this.isScraped; + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/ActionPlanService.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/ActionPlanService.java index d4f71a5..021abdb 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/ActionPlanService.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/ActionPlanService.java @@ -32,4 +32,6 @@ public interface ActionPlanService { //* 완료한 액션 플랜 목록 조회 List getFinishedActionPlan(Long memberId); + + void toggleActionPlanScrapStatus(Long actionpalnId); } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/Impl/ActionPlanServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/Impl/ActionPlanServiceImpl.java index f1c540e..cf8f648 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/Impl/ActionPlanServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/service/Impl/ActionPlanServiceImpl.java @@ -110,4 +110,11 @@ public List getFinishedActionPlan(Long memberI .map(actionPlan -> FinishedActionPlanGetResponseDto.of(actionPlan.getId(), actionPlan.getContent(), actionPlan.getIsScraped(),actionPlan.getSeed().getId())) .collect(Collectors.toList()); } + + @Override + @Transactional + public void toggleActionPlanScrapStatus(Long actionpalnId) { + ActionPlan actionPlan = actionPlanRepository.findActionPlanByIdOrThrow(actionpalnId); + actionPlan.toggleScrapStatus(); + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/response/SuccessStatus.java b/growthookServer/src/main/java/com/example/growthookserver/common/response/SuccessStatus.java index db6ce4b..2afd550 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/response/SuccessStatus.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/response/SuccessStatus.java @@ -50,6 +50,7 @@ public enum SuccessStatus { GET_FINISHED_ACTIONPLAN_PERCENT(HttpStatus.OK, "완료한 액션 플랜 퍼센트 조회 성공"), GET_DOING_ACTIONPLAN_SUCCESS(HttpStatus.OK, "진행 중인 액션 플랜 리스트 조회 성공"), GET_FINISHED_ACTIONPLAN_SUCCESS(HttpStatus.OK,"완료한 액션 플랜 리스트 조회 성공"), + TOGGLE_ACTIONPLAN_SCRAP(HttpStatus.OK, "액션 플랜 스크랩 여부 토글 전환 성공"), /** * review From aac79cd7d893d0f96993dae5876bb67151566b8f Mon Sep 17 00:00:00 2001 From: Hong0329 Date: Tue, 16 Jan 2024 18:45:39 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[FIX]=EC=95=A1=EC=85=98=20=ED=94=8C?= =?UTF-8?q?=EB=9E=9C=EC=97=90=20=EB=A6=AC=EB=B7=B0=EA=B0=80=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EC=A7=80=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=AA=85?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/actionplan/dto/response/ActionPlanGetResponseDto.java | 2 +- .../actionplan/dto/response/DoingActionPlanGetResponseDto.java | 2 +- .../dto/response/FinishedActionPlanGetResponseDto.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java index f34ccf1..11c053c 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/ActionPlanGetResponseDto.java @@ -18,5 +18,5 @@ public class ActionPlanGetResponseDto { private Boolean isFinished; - private Boolean isReviewed; + private Boolean hasReview; } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java index 6bdb694..fa9d6e1 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/DoingActionPlanGetResponseDto.java @@ -17,5 +17,5 @@ public class DoingActionPlanGetResponseDto { private Long seedId; - private Boolean isReviewed; + private Boolean hasReview; } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java index a044521..fe5bb62 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/dto/response/FinishedActionPlanGetResponseDto.java @@ -17,5 +17,5 @@ public class FinishedActionPlanGetResponseDto { private Long seedId; - private Boolean isReviewed; + private Boolean hasReview; } From 1cf89903fe3db5ca3d24a5f3cb253b5a5b5f99e8 Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Wed, 17 Jan 2024 03:24:35 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[ADD]=20JWT=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/SecurityConfig.java | 36 ++++--- .../jwt/JwtAuthenticationEntryPoint.java | 25 +++++ .../config/jwt/JwtAuthenticationFilter.java | 51 ++++++++++ .../common/config/jwt/JwtExceptionType.java | 10 ++ .../common/config/jwt/JwtTokenProvider.java | 95 +++++++++++++++++++ .../common/config/jwt/UserAuthentication.java | 16 ++++ 6 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtExceptionType.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/UserAuthentication.java diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java index dd11568..f6f8dbe 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java @@ -1,12 +1,18 @@ package com.example.growthookserver.common.config; +import com.example.growthookserver.common.config.jwt.JwtAuthenticationEntryPoint; +import com.example.growthookserver.common.config.jwt.JwtAuthenticationFilter; +import com.example.growthookserver.common.config.jwt.JwtTokenProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; @Configuration @@ -14,6 +20,9 @@ @EnableWebSecurity public class SecurityConfig { + private final JwtTokenProvider jwtTokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private static final String[] SWAGGER_URL = { "/swagger-resources/**", "/favicon.ico", @@ -28,19 +37,20 @@ public class SecurityConfig { @Bean @Profile("dev") SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ - // XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler(); -// http -// .csrf((csrf) -> csrf -// .csrfTokenRequestHandler(requestHandler) -// ) -// .authorizeRequests() -// .anyRequest().permitAll(); - http - .csrf().disable() - .httpBasic().disable() - .authorizeHttpRequests() - .anyRequest().permitAll(); - + http.csrf((csrfConfig) -> csrfConfig.disable()) + .cors(Customizer.withDefaults()) + .sessionManagement( + (sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests( + authorize -> authorize + .requestMatchers(SWAGGER_URL).permitAll() + .anyRequest().authenticated()) + .addFilterBefore( + new JwtAuthenticationFilter(this.jwtTokenProvider, this.jwtAuthenticationEntryPoint), + UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)); return http.build(); } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..01869f6 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,25 @@ +package com.example.growthookserver.common.config.jwt; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + setResponse(response); + } + + + public void setResponse(HttpServletResponse response) throws IOException { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..b8beb7f --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,51 @@ +package com.example.growthookserver.common.config.jwt; + + +import static com.example.growthookserver.common.config.jwt.JwtExceptionType.VALID_JWT_TOKEN; + +import com.example.growthookserver.common.response.ErrorStatus; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain chain) throws ServletException, IOException { + String accessToken = jwtTokenProvider.resolveToken(request); + + if (accessToken != null) { + // 토큰 검증 + if (jwtTokenProvider.validateToken(accessToken) + == VALID_JWT_TOKEN) { // 토큰이 존재하고 유효한 토큰일 때만 + Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken); + UserAuthentication authentication = new UserAuthentication(userId, null, + null); //사용자 객체 생성 + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( + request)); // request 정보로 사용자 객체 디테일 설정 + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + jwtAuthenticationEntryPoint.commence(request, response, + new AuthenticationException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()) { + }); + return; + } + } + chain.doFilter(request, response); + } +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtExceptionType.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtExceptionType.java new file mode 100644 index 0000000..6464d58 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtExceptionType.java @@ -0,0 +1,10 @@ +package com.example.growthookserver.common.config.jwt; + +public enum JwtExceptionType { + VALID_JWT_TOKEN, // 유효한 JWT + INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 + INVALID_JWT_TOKEN, // 유효하지 않은 토큰 + EXPIRED_JWT_TOKEN, // 만료된 토큰 + UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 + EMPTY_JWT // 빈 JWT +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java new file mode 100644 index 0000000..2047a7a --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java @@ -0,0 +1,95 @@ +package com.example.growthookserver.common.config.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.UnsupportedJwtException; +import jakarta.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Date; +import javax.crypto.spec.SecretKeySpec; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtTokenProvider { + + @Value("${jwt.secret}") + private String secretKey; + + @Value("${jwt.access-token.expire-length}") + private Long accessTokenExpireLength; + + private static final String AUTHORIZATION_HEADER = "Authorization"; + + public String generateAccessToken(Authentication authentication) { + Date now = new Date(); + Date expiration = new Date(now.getTime() + accessTokenExpireLength); + + final Claims claims = Jwts.claims() + .setIssuedAt(now) + .setExpiration(expiration); + + claims.put("id", authentication.getPrincipal()); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setClaims(claims) + .signWith(getSignKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public Integer getAccessTokenPayload(String token) { + return Integer.parseInt( + Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody().get("id").toString()); + } + + public String resolveToken(HttpServletRequest request) { + + String header = request.getHeader(AUTHORIZATION_HEADER); + + if (header == null || !header.startsWith("Bearer ")) { + return null; + } else { + return header.split(" ")[1]; + } + } + + public JwtExceptionType validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody(); + return JwtExceptionType.VALID_JWT_TOKEN; + } catch (io.jsonwebtoken.security.SignatureException exception) { + log.error("잘못된 JWT 서명을 가진 토큰입니다."); + return JwtExceptionType.INVALID_JWT_SIGNATURE; + } catch (MalformedJwtException exception) { + log.error("잘못된 JWT 토큰입니다."); + return JwtExceptionType.INVALID_JWT_TOKEN; + } catch (ExpiredJwtException exception) { + log.error("만료된 JWT 토큰입니다."); + return JwtExceptionType.EXPIRED_JWT_TOKEN; + } catch (UnsupportedJwtException exception) { + log.error("지원하지 않는 JWT 토큰입니다."); + return JwtExceptionType.UNSUPPORTED_JWT_TOKEN; + } catch (IllegalArgumentException exception) { + log.error("JWT Claims가 비어있습니다."); + return JwtExceptionType.EMPTY_JWT; + } + } + + private Key getSignKey() { + byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); + return new SecretKeySpec(keyBytes, "HmacSHA256"); + } +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/UserAuthentication.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/UserAuthentication.java new file mode 100644 index 0000000..12cfacb --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/UserAuthentication.java @@ -0,0 +1,16 @@ +package com.example.growthookserver.common.config.jwt; + + +import java.util.Collection; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +// UsernamePasswordAuthenticationToken: 사용자의 인증 정보 저장하고 전달 +public class UserAuthentication extends UsernamePasswordAuthenticationToken { + + // 사용자 인증 객체 생성 + public UserAuthentication(Object principal, Object credentials, + Collection authorities) { + super(principal, credentials, authorities); + } +} From c24323bf2be23ff7ec2190773262dd0bc2540c7a Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Wed, 17 Jan 2024 04:25:20 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[FEAT]=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 53 +++++++++ .../auth/dto/Request/AuthRequestDto.java | 13 +++ .../auth/dto/Response/AuthResponseDto.java | 20 ++++ .../dto/Response/AuthTokenResponseDto.java | 17 +++ .../api/member/auth/dto/SocialInfoDto.java | 12 ++ .../api/member/auth/service/AuthService.java | 13 +++ .../auth/service/Impl/AuthServiceImpl.java | 103 ++++++++++++++++++ .../member/auth/service/KakaoAuthService.java | 49 +++++++++ .../api/member/domain/Member.java | 16 +-- .../member/repository/MemberRepository.java | 5 + .../common/config/SwaggerConfig.java | 8 ++ .../common/config/jwt/JwtTokenProvider.java | 25 +++++ 12 files changed, 326 insertions(+), 8 deletions(-) create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/controller/AuthController.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthResponseDto.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AuthService.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/controller/AuthController.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/controller/AuthController.java new file mode 100644 index 0000000..17bff6b --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/controller/AuthController.java @@ -0,0 +1,53 @@ +package com.example.growthookserver.api.member.auth.controller; + +import com.example.growthookserver.api.member.auth.dto.Request.AuthRequestDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthResponseDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto; +import com.example.growthookserver.api.member.auth.service.AuthService; +import com.example.growthookserver.common.config.jwt.JwtTokenProvider; +import com.example.growthookserver.common.response.ApiResponse; +import com.example.growthookserver.common.response.SuccessStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/auth") +@RequiredArgsConstructor +@Tag(name = "Auth - 인증/인가 관련 API", description = "Auth API Document") +public class AuthController { + private final AuthService authService; + private final JwtTokenProvider jwtTokenProvider; + + @PostMapping() + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "SocialLogin", description = "소셜 로그인 API입니다.") + public ApiResponse socialLogin(@RequestBody AuthRequestDto authRequestDto) throws NoSuchAlgorithmException, InvalidKeySpecException { + + AuthResponseDto responseDto = authService.socialLogin(authRequestDto); + return ApiResponse.success(SuccessStatus.SIGNIN_SUCCESS, responseDto); + + } + + @GetMapping("/token") + @ResponseStatus(HttpStatus.OK) + @Operation(summary = "TokenRefresh", description = "토큰 재발급 API입니다.") + public ApiResponse getNewToken(HttpServletRequest request) { + String accessToken = (String) request.getAttribute("newAccessToken"); + String refreshToken = jwtTokenProvider.resolveRefreshToken(request); + + return ApiResponse.success(SuccessStatus.GET_NEW_TOKEN_SUCCESS, authService.getNewToken(accessToken, refreshToken)); + } +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java new file mode 100644 index 0000000..7b31afc --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java @@ -0,0 +1,13 @@ +package com.example.growthookserver.api.member.auth.dto.Request; + +import static lombok.AccessLevel.PROTECTED; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = PROTECTED) +public class AuthRequestDto { + private String socialPlatform; + private String socialToken; +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthResponseDto.java new file mode 100644 index 0000000..a881d36 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthResponseDto.java @@ -0,0 +1,20 @@ +package com.example.growthookserver.api.member.auth.dto.Response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor(staticName = "of") +public class AuthResponseDto { + + private String nickname; + + private Long memberId; + + private String accessToken; + + private String refreshToken; + +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java new file mode 100644 index 0000000..148156f --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java @@ -0,0 +1,17 @@ +package com.example.growthookserver.api.member.auth.dto.Response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class AuthTokenResponseDto { + private String accessToken; + + private String refreshToken; + public static AuthTokenResponseDto of (String accessToken, String refreshToken) { + return new AuthTokenResponseDto(accessToken, refreshToken); + } +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java new file mode 100644 index 0000000..a3ec844 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java @@ -0,0 +1,12 @@ +package com.example.growthookserver.api.member.auth.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SocialInfoDto { + private String id; + private String nickname; + private String email; +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AuthService.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AuthService.java new file mode 100644 index 0000000..f46d36e --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AuthService.java @@ -0,0 +1,13 @@ +package com.example.growthookserver.api.member.auth.service; + +import com.example.growthookserver.api.member.auth.dto.Request.AuthRequestDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthResponseDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +public interface AuthService { + AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchAlgorithmException, InvalidKeySpecException; + + AuthTokenResponseDto getNewToken(String accessToken, String refreshToken); +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java new file mode 100644 index 0000000..4b65d26 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java @@ -0,0 +1,103 @@ +package com.example.growthookserver.api.member.auth.service.Impl; + +import com.example.growthookserver.api.member.auth.SocialPlatform; +import com.example.growthookserver.api.member.auth.dto.Request.AuthRequestDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthResponseDto; +import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto; + +import com.example.growthookserver.api.member.auth.dto.SocialInfoDto; +import com.example.growthookserver.api.member.auth.service.AuthService; +import com.example.growthookserver.api.member.auth.service.KakaoAuthService; + +import com.example.growthookserver.api.member.domain.Member; +import com.example.growthookserver.api.member.repository.MemberRepository; +import com.example.growthookserver.common.config.jwt.JwtTokenProvider; +import com.example.growthookserver.common.config.jwt.UserAuthentication; +import com.example.growthookserver.common.exception.BadRequestException; +import com.example.growthookserver.common.response.ErrorStatus; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + private final JwtTokenProvider jwtTokenProvider; + private final KakaoAuthService kakaoAuthService; + private final MemberRepository memberRepository; + + @Override + @Transactional + public AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchAlgorithmException, InvalidKeySpecException { + + if (authRequestDto.getSocialPlatform() == null || authRequestDto.getSocialToken() == null) { + throw new BadRequestException(ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION.getMessage()); + } + + try { + SocialPlatform socialPlatform = SocialPlatform.valueOf(authRequestDto.getSocialPlatform()); + + SocialInfoDto socialData = getSocialData(socialPlatform, authRequestDto.getSocialToken()); + + String refreshToken = jwtTokenProvider.generateRefreshToken(); + + Boolean isExistUser = isMemberBySocialId(socialData.getId()); + + // 신규 유저 저장 + if (!isExistUser.booleanValue()) { + Member member = Member.builder() + .nickname(socialData.getNickname()) + .email(socialData.getEmail()) + .socialPlatform(socialPlatform) + .socialId(socialData.getId()) + .build(); + + memberRepository.save(member); + + member.updateRefreshToken(refreshToken); + } + else findMemberBySocialId(socialData.getId()).updateRefreshToken(refreshToken); + + // socialId를 통해서 등록된 유저 찾기 + Member signedMember = findMemberBySocialId(socialData.getId()); + + Authentication authentication = new UserAuthentication(signedMember.getId(), null, null); + + String accessToken = jwtTokenProvider.generateAccessToken(authentication); + + return AuthResponseDto.of(signedMember.getNickname(), signedMember.getId(), accessToken, signedMember.getRefreshToken()); + + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException(ErrorStatus.ANOTHER_ACCESS_TOKEN.getMessage()); + } + } + + @Override + @Transactional + public AuthTokenResponseDto getNewToken(String accessToken, String refreshToken) { + return AuthTokenResponseDto.of(accessToken,refreshToken); + } + + private Member findMemberBySocialId(String socialId) { + return memberRepository.findBySocialId(socialId) + .orElseThrow(() -> new BadRequestException(ErrorStatus.INVALID_MEMBER.getMessage())); + } + + private boolean isMemberBySocialId(String socialId) { + return memberRepository.existsBySocialId(socialId); + } + + private SocialInfoDto getSocialData(SocialPlatform socialPlatform, String socialAccessToken) throws NoSuchAlgorithmException, InvalidKeySpecException { + + switch (socialPlatform) { + case KAKAO: + return kakaoAuthService.login(socialAccessToken); + default: + throw new IllegalArgumentException(ErrorStatus.ANOTHER_ACCESS_TOKEN.getMessage()); + } + } +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java new file mode 100644 index 0000000..9131c39 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java @@ -0,0 +1,49 @@ +package com.example.growthookserver.api.member.auth.service; + +import com.example.growthookserver.api.member.auth.dto.SocialInfoDto; +import com.example.growthookserver.common.exception.BaseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@RequiredArgsConstructor +@Component +public class KakaoAuthService { + + //이 login은 카카오 서버에 AccessToken으로 접속할때의 login + public SocialInfoDto login(String socialAccessToken) { + return getKakaoSocialData(socialAccessToken); + } + + private SocialInfoDto getKakaoSocialData(String socialAccessToken) { + + try{ + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", socialAccessToken); + HttpEntity httpEntity = new HttpEntity<>(headers); + ResponseEntity responseData = restTemplate.postForEntity("https://kapi.kakao.com/v2/user/me", httpEntity, String.class); + ObjectMapper objectMapper = new ObjectMapper(); + + JsonNode jsonNode = objectMapper.readTree(responseData.getBody()); + + String nickname = jsonNode.get("kakao_account").get("profile").get("nickname").asText(); + String email = jsonNode.get("email").asText(); + String kakaoId = jsonNode.get("id").asText(); + + return new SocialInfoDto(kakaoId, nickname, email); + } catch (JsonProcessingException e) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "카카오 계정 데이터 가공 실패"); + } + + } + +} \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java index 3ce450c..e0c66c7 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java @@ -27,9 +27,6 @@ public class Member extends BaseTimeEntity { @Column(name = "social_platform") private SocialPlatform socialPlatform; - @Column(name = "is_new_member") - private Boolean isNewMember; - @Column(name = "used_ssuk") private Integer usedSsuk; @@ -43,16 +40,15 @@ public class Member extends BaseTimeEntity { private String refreshToken; @Builder - public Member(String nickname, String email, SocialPlatform socialPlatform, Boolean isNewMember, Integer usedSsuk, Integer gatheredSsuk) { + public Member(String nickname, String email, SocialPlatform socialPlatform, String socialId) { this.nickname = nickname; this.email = email; this.socialPlatform = socialPlatform; - this.isNewMember = isNewMember; - this.usedSsuk = usedSsuk; - this.gatheredSsuk = gatheredSsuk; + this.socialId = socialId; + this.usedSsuk = 0; + this.gatheredSsuk = 0; } - @Builder public void incrementGatheredSsuk() { this.gatheredSsuk = (this.gatheredSsuk == null ? 0 : this.gatheredSsuk) + 1; } @@ -61,4 +57,8 @@ public void useSsuck() { this.gatheredSsuk--; this.usedSsuk++; } + + public void updateRefreshToken (String refreshToken) { + this.refreshToken = refreshToken; + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java index c662aa9..43617d1 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java @@ -10,6 +10,11 @@ public interface MemberRepository extends JpaRepository { Optional findMemberById(Long id); + boolean existsBySocialId(String socialId); + Optional findByIdAndRefreshToken(Long memberId, String refreshToken); + + Optional findBySocialId(String socialId); + default Member findMemberByIdOrThrow(Long memberId){ return findMemberById(memberId) .orElseThrow(() -> new NotFoundException(ErrorStatus.NOT_FOUND_MEMBER.getMessage())); diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/SwaggerConfig.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/SwaggerConfig.java index c38becf..93ea42a 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/SwaggerConfig.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/SwaggerConfig.java @@ -1,7 +1,9 @@ package com.example.growthookserver.common.config; import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityScheme; import lombok.RequiredArgsConstructor; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; @@ -13,6 +15,12 @@ version = "v1")) @RequiredArgsConstructor @Configuration +@SecurityScheme( + name = "JWT Authentication", + type = SecuritySchemeType.HTTP, + bearerFormat = "JWT", + scheme = "bearer" +) public class SwaggerConfig { @Bean diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java index 2047a7a..7c3d867 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java @@ -29,7 +29,11 @@ public class JwtTokenProvider { @Value("${jwt.access-token.expire-length}") private Long accessTokenExpireLength; + @Value("${jwt.refresh-token.expire-length}") + private Long refreshTokenExpireLength; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String REFRESH_AUTHORIZATION_HEADER = "Refresh"; public String generateAccessToken(Authentication authentication) { Date now = new Date(); @@ -48,6 +52,17 @@ public String generateAccessToken(Authentication authentication) { .compact(); } + public String generateRefreshToken() { + Date now = new Date(); + Date expiration = new Date(now.getTime() + refreshTokenExpireLength); + + return Jwts.builder() + .setIssuedAt(now) + .setExpiration(expiration) + .signWith(getSignKey(), SignatureAlgorithm.HS256) + .compact(); + } + public Integer getAccessTokenPayload(String token) { return Integer.parseInt( Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) @@ -65,6 +80,16 @@ public String resolveToken(HttpServletRequest request) { } } + public String resolveRefreshToken(HttpServletRequest request) { + String header = request.getHeader(REFRESH_AUTHORIZATION_HEADER); + + if (header == null || !header.startsWith("Bearer ")) { + return null; + } else { + return header.split(" ")[1]; + } + } + public JwtExceptionType validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) From ecff2a56ed598259d0bbdf1a3846c2a99c864b27 Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Wed, 17 Jan 2024 13:26:15 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[FEAT]=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=8F=20?= =?UTF-8?q?swagger=20JWT=20=EC=9D=B8=EC=A6=9D=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actionplan/controller/ActionPlanController.java | 2 ++ .../api/cave/controller/CaveController.java | 2 ++ .../api/member/auth/dto/SocialInfoDto.java | 1 + .../member/auth/service/Impl/AuthServiceImpl.java | 1 + .../api/member/auth/service/KakaoAuthService.java | 10 ++++++++-- .../api/member/controller/MemberController.java | 2 ++ .../growthookserver/api/member/domain/Member.java | 6 +++++- .../api/review/controller/ReviewController.java | 2 ++ .../api/seed/controller/SeedController.java | 2 ++ .../common/config/SecurityConfig.java | 12 ++++++++++++ 10 files changed, 37 insertions(+), 3 deletions(-) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java index e51a494..d80e439 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/actionplan/controller/ActionPlanController.java @@ -9,6 +9,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; 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 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "AciontPlan - 액션플랜 관련 API",description = "AcitonPlan APi Documnet") public class ActionPlanController { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/cave/controller/CaveController.java b/growthookServer/src/main/java/com/example/growthookserver/api/cave/controller/CaveController.java index e601e16..08db317 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/cave/controller/CaveController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/cave/controller/CaveController.java @@ -9,6 +9,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; 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 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "Cave - 동굴 관련 API", description = "Cave API Document") public class CaveController { private final CaveService caveService; diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java index a3ec844..3323558 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/SocialInfoDto.java @@ -9,4 +9,5 @@ public class SocialInfoDto { private String id; private String nickname; private String email; + private String profileImage; } \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java index 4b65d26..62f1fdf 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java @@ -54,6 +54,7 @@ public AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchA .email(socialData.getEmail()) .socialPlatform(socialPlatform) .socialId(socialData.getId()) + .profileImage(socialData.getProfileImage()) .build(); memberRepository.save(member); diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java index 9131c39..0374827 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/KakaoAuthService.java @@ -36,10 +36,16 @@ private SocialInfoDto getKakaoSocialData(String socialAccessToken) { JsonNode jsonNode = objectMapper.readTree(responseData.getBody()); String nickname = jsonNode.get("kakao_account").get("profile").get("nickname").asText(); - String email = jsonNode.get("email").asText(); + String profileImage; + try { + profileImage = jsonNode.get("kakao_account").get("profile").get("profile_image_url").asText(); + } catch (NullPointerException e) { + profileImage = null; + } + String email = jsonNode.get("kakao_account").get("email").asText(); String kakaoId = jsonNode.get("id").asText(); - return new SocialInfoDto(kakaoId, nickname, email); + return new SocialInfoDto(kakaoId, nickname, email, profileImage); } catch (JsonProcessingException e) { throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "카카오 계정 데이터 가공 실패"); } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/controller/MemberController.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/controller/MemberController.java index f8b1fca..29961bd 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/controller/MemberController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/controller/MemberController.java @@ -7,6 +7,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -20,6 +21,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "Member - 유저 관련 API", description = "Member API Document") public class MemberController { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java index e0c66c7..912c65f 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/domain/Member.java @@ -23,6 +23,9 @@ public class Member extends BaseTimeEntity { @Column private String email; + @Column + private String profileImage; + @Enumerated(value = EnumType.STRING) @Column(name = "social_platform") private SocialPlatform socialPlatform; @@ -40,11 +43,12 @@ public class Member extends BaseTimeEntity { private String refreshToken; @Builder - public Member(String nickname, String email, SocialPlatform socialPlatform, String socialId) { + public Member(String nickname, String email, SocialPlatform socialPlatform, String socialId, String profileImage) { this.nickname = nickname; this.email = email; this.socialPlatform = socialPlatform; this.socialId = socialId; + this.profileImage = profileImage; this.usedSsuk = 0; this.gatheredSsuk = 0; } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/review/controller/ReviewController.java b/growthookServer/src/main/java/com/example/growthookserver/api/review/controller/ReviewController.java index 74f34e3..4b174fe 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/review/controller/ReviewController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/review/controller/ReviewController.java @@ -6,6 +6,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; 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; @@ -21,6 +22,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "Review - 리뷰 관련 API", description = "Review API Document") public class ReviewController { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/seed/controller/SeedController.java b/growthookServer/src/main/java/com/example/growthookserver/api/seed/controller/SeedController.java index 0c4b4dc..75a8f4c 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/seed/controller/SeedController.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/seed/controller/SeedController.java @@ -12,6 +12,7 @@ import com.example.growthookserver.common.response.ApiResponse; import com.example.growthookserver.common.response.SuccessStatus; 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 java.util.List; @@ -22,6 +23,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("api/v1") +@SecurityRequirement(name = "JWT Authentication") @Tag(name = "Seed - 인사이트 관련 API", description = "Seed API Document") public class SeedController { diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java index f6f8dbe..5de7896 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/SecurityConfig.java @@ -3,6 +3,7 @@ import com.example.growthookserver.common.config.jwt.JwtAuthenticationEntryPoint; import com.example.growthookserver.common.config.jwt.JwtAuthenticationFilter; import com.example.growthookserver.common.config.jwt.JwtTokenProvider; +import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -14,6 +15,9 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @RequiredArgsConstructor @@ -34,6 +38,13 @@ public class SecurityConfig { "/swagger-ui/swagger-ui.css", }; + private static final String[] AUTH_WHITELIST = { + "/api/v1/auth", + "/health", + "/profile", + "/actuator/**" + }; + @Bean @Profile("dev") SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ @@ -45,6 +56,7 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ .authorizeHttpRequests( authorize -> authorize .requestMatchers(SWAGGER_URL).permitAll() + .requestMatchers(AUTH_WHITELIST).permitAll() .anyRequest().authenticated()) .addFilterBefore( new JwtAuthenticationFilter(this.jwtTokenProvider, this.jwtAuthenticationEntryPoint), From 0777bc2a7514a0235bfa9e9ccee48dae34880c4d Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Wed, 17 Jan 2024 13:35:29 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[CHORE]=20static=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=ED=98=95=EC=8B=9D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/member/auth/dto/Response/AuthTokenResponseDto.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java index 148156f..773159f 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Response/AuthTokenResponseDto.java @@ -6,12 +6,9 @@ @Getter @NoArgsConstructor -@AllArgsConstructor +@AllArgsConstructor(staticName = "of") public class AuthTokenResponseDto { private String accessToken; private String refreshToken; - public static AuthTokenResponseDto of (String accessToken, String refreshToken) { - return new AuthTokenResponseDto(accessToken, refreshToken); - } } From fb8e9317c2ec2153a6d9ae770aff766df2aefaff Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Wed, 17 Jan 2024 13:52:17 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[REFACTOR]=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C,=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20repository=EB=A1=9C?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/auth/service/Impl/AuthServiceImpl.java | 15 +++------------ .../api/member/repository/MemberRepository.java | 6 ++++++ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java index 62f1fdf..8c2afc5 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java @@ -45,7 +45,7 @@ public AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchA String refreshToken = jwtTokenProvider.generateRefreshToken(); - Boolean isExistUser = isMemberBySocialId(socialData.getId()); + Boolean isExistUser = memberRepository.existsBySocialId(socialData.getId()); // 신규 유저 저장 if (!isExistUser.booleanValue()) { @@ -61,10 +61,10 @@ public AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchA member.updateRefreshToken(refreshToken); } - else findMemberBySocialId(socialData.getId()).updateRefreshToken(refreshToken); + else memberRepository.findMemberBySocialIdOrThrow(socialData.getId()).updateRefreshToken(refreshToken); // socialId를 통해서 등록된 유저 찾기 - Member signedMember = findMemberBySocialId(socialData.getId()); + Member signedMember = memberRepository.findMemberBySocialIdOrThrow(socialData.getId()); Authentication authentication = new UserAuthentication(signedMember.getId(), null, null); @@ -83,15 +83,6 @@ public AuthTokenResponseDto getNewToken(String accessToken, String refreshToken) return AuthTokenResponseDto.of(accessToken,refreshToken); } - private Member findMemberBySocialId(String socialId) { - return memberRepository.findBySocialId(socialId) - .orElseThrow(() -> new BadRequestException(ErrorStatus.INVALID_MEMBER.getMessage())); - } - - private boolean isMemberBySocialId(String socialId) { - return memberRepository.existsBySocialId(socialId); - } - private SocialInfoDto getSocialData(SocialPlatform socialPlatform, String socialAccessToken) throws NoSuchAlgorithmException, InvalidKeySpecException { switch (socialPlatform) { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java index 43617d1..8c63c37 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java @@ -1,6 +1,7 @@ package com.example.growthookserver.api.member.repository; import com.example.growthookserver.api.member.domain.Member; +import com.example.growthookserver.common.exception.BadRequestException; import com.example.growthookserver.common.exception.NotFoundException; import com.example.growthookserver.common.response.ErrorStatus; import org.springframework.data.jpa.repository.JpaRepository; @@ -19,4 +20,9 @@ default Member findMemberByIdOrThrow(Long memberId){ return findMemberById(memberId) .orElseThrow(() -> new NotFoundException(ErrorStatus.NOT_FOUND_MEMBER.getMessage())); } + + default Member findMemberBySocialIdOrThrow(String socialId) { + return findBySocialId(socialId) + .orElseThrow(() -> new BadRequestException(ErrorStatus.INVALID_MEMBER.getMessage())); + } } From 9f58d4839fab8c43ae29ce1e921db1f57ef766db Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Thu, 18 Jan 2024 21:15:48 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[FEAT]=20=EC=95=A0=ED=94=8C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- growthookServer/build.gradle | 2 + .../auth/dto/Request/AuthRequestDto.java | 1 + .../member/auth/service/AppleAuthService.java | 126 ++++++++++++++++++ .../auth/service/Impl/AuthServiceImpl.java | 9 +- .../common/response/ErrorStatus.java | 4 +- 5 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AppleAuthService.java diff --git a/growthookServer/build.gradle b/growthookServer/build.gradle index 0875644..7f17770 100644 --- a/growthookServer/build.gradle +++ b/growthookServer/build.gradle @@ -46,6 +46,8 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + + implementation 'com.google.code.gson:gson:2.10.1' } tasks.named('bootBuildImage') { diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java index 7b31afc..5c919d5 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/dto/Request/AuthRequestDto.java @@ -10,4 +10,5 @@ public class AuthRequestDto { private String socialPlatform; private String socialToken; + private String userName; } \ No newline at end of file diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AppleAuthService.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AppleAuthService.java new file mode 100644 index 0000000..5ce8093 --- /dev/null +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/AppleAuthService.java @@ -0,0 +1,126 @@ +package com.example.growthookserver.api.member.auth.service; + +import com.example.growthookserver.api.member.auth.dto.SocialInfoDto; +import com.example.growthookserver.common.exception.BaseException; +import com.google.gson.*; +import com.example.growthookserver.common.exception.UnAuthorizedException; +import com.example.growthookserver.common.response.ErrorStatus; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.URL; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Base64; +import java.util.Objects; +import lombok.RequiredArgsConstructor; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AppleAuthService { + /** + * 1. apple로 부터 공개키 3개 가져옴 + * 2. 내가 클라에서 가져온 token String과 비교해서 써야할 공개키 확인 (kid,alg 값 같은 것) + * 3. 그 공개키 재료들로 공개키 만들고, 이 공개키로 JWT토큰 부분의 바디 부분의 decode하면 유저 정보 + */ + public SocialInfoDto login(String socialAccessToken, String userName) { + return getAppleSocialData(socialAccessToken, userName); + } + + private JsonArray getApplePublicKeys() { + StringBuffer result = new StringBuffer(); + try { + URL url = new URL("https://appleid.apple.com/auth/keys"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + + String line = ""; + + while ((line = br.readLine()) != null) { + result.append(line); + } + JsonObject keys = (JsonObject) JsonParser.parseString(result.toString()); + return (JsonArray) keys.get("keys"); // 1. 공개키 가져오기 + } catch (IOException e) { + throw new UnAuthorizedException(ErrorStatus.FAILED_TO_VALIDATE_APPLE_LOGIN.getMessage()); + } + } + + private SocialInfoDto getAppleSocialData(String socialAccessToken, String userName) { + try { + JsonArray publicKeyList = getApplePublicKeys(); + PublicKey publicKey = makePublicKey(socialAccessToken, publicKeyList); + + Claims userInfo = Jwts.parserBuilder() + .setSigningKey(publicKey) + .build() + .parseClaimsJws(socialAccessToken.substring(7)) + .getBody(); + + JsonObject userInfoObject = (JsonObject) JsonParser.parseString(new Gson().toJson(userInfo)); + String appleId = userInfoObject.get("sub").getAsString(); + String email = userInfoObject.get("email").getAsString(); + + return new SocialInfoDto(appleId, userName, email, null); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new BaseException(HttpStatus.INTERNAL_SERVER_ERROR, "애플 계정 데이터 가공 실패"); + } + } + + private PublicKey makePublicKey(String identityToken, JsonArray publicKeyList) throws NoSuchAlgorithmException, InvalidKeySpecException { + JsonObject selectedObject = null; + + String[] decodeArray = identityToken.split("\\."); + String header = new String(Base64.getDecoder().decode(decodeArray[0].substring(7))); + + JsonElement kid = ((JsonObject) JsonParser.parseString(header)).get("kid"); + JsonElement alg = ((JsonObject) JsonParser.parseString(header)).get("alg"); + + for (JsonElement publicKey : publicKeyList) { + JsonObject publicKeyObject = publicKey.getAsJsonObject(); + JsonElement publicKid = publicKeyObject.get("kid"); + JsonElement publicAlg = publicKeyObject.get("alg"); + + if (Objects.equals(kid, publicKid) && Objects.equals(alg, publicAlg)) { + selectedObject = publicKeyObject; + break; + } + } + + if (selectedObject == null) { + throw new InvalidKeySpecException("공개키를 찾을 수 없습니다."); + } + + return getPublicKey(selectedObject); + } + + private PublicKey getPublicKey(JsonObject object) throws NoSuchAlgorithmException, InvalidKeySpecException { + String nStr = object.get("n").toString(); + String eStr = object.get("e").toString(); + + byte[] nBytes = Base64.getUrlDecoder().decode(nStr.substring(1, nStr.length() - 1)); + byte[] eBytes = Base64.getUrlDecoder().decode(eStr.substring(1, eStr.length() - 1)); + + BigInteger n = new BigInteger(1, nBytes); + BigInteger e = new BigInteger(1, eBytes); + + + RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + return publicKey; + } + +} diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java index 8c2afc5..18b4ff2 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java @@ -6,6 +6,7 @@ import com.example.growthookserver.api.member.auth.dto.Response.AuthTokenResponseDto; import com.example.growthookserver.api.member.auth.dto.SocialInfoDto; +import com.example.growthookserver.api.member.auth.service.AppleAuthService; import com.example.growthookserver.api.member.auth.service.AuthService; import com.example.growthookserver.api.member.auth.service.KakaoAuthService; @@ -28,6 +29,7 @@ public class AuthServiceImpl implements AuthService { private final JwtTokenProvider jwtTokenProvider; private final KakaoAuthService kakaoAuthService; + private final AppleAuthService appleAuthService; private final MemberRepository memberRepository; @Override @@ -41,7 +43,8 @@ public AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchA try { SocialPlatform socialPlatform = SocialPlatform.valueOf(authRequestDto.getSocialPlatform()); - SocialInfoDto socialData = getSocialData(socialPlatform, authRequestDto.getSocialToken()); + SocialInfoDto socialData = getSocialData(socialPlatform, authRequestDto.getSocialToken(), + authRequestDto.getUserName()); String refreshToken = jwtTokenProvider.generateRefreshToken(); @@ -83,11 +86,13 @@ public AuthTokenResponseDto getNewToken(String accessToken, String refreshToken) return AuthTokenResponseDto.of(accessToken,refreshToken); } - private SocialInfoDto getSocialData(SocialPlatform socialPlatform, String socialAccessToken) throws NoSuchAlgorithmException, InvalidKeySpecException { + private SocialInfoDto getSocialData(SocialPlatform socialPlatform, String socialAccessToken, String userName) { switch (socialPlatform) { case KAKAO: return kakaoAuthService.login(socialAccessToken); + case APPLE: + return appleAuthService.login(socialAccessToken, userName); default: throw new IllegalArgumentException(ErrorStatus.ANOTHER_ACCESS_TOKEN.getMessage()); } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/response/ErrorStatus.java b/growthookServer/src/main/java/com/example/growthookserver/common/response/ErrorStatus.java index a63ca96..8c79e44 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/response/ErrorStatus.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/response/ErrorStatus.java @@ -25,6 +25,7 @@ public enum ErrorStatus { KAKAO_UNAUTHORIZED_USER("카카오 로그인 실패. 만료되었거나 잘못된 카카오 토큰입니다."), SIGNIN_REQUIRED("access, refreshToken 모두 만료되었습니다. 재로그인이 필요합니다."), VALID_ACCESS_TOKEN("아직 유효한 accessToken 입니다."), + FAILED_TO_VALIDATE_APPLE_LOGIN("애플 로그인 실패"), /** * 404 NOT_FOUND @@ -40,7 +41,8 @@ public enum ErrorStatus { * 500 SERVER_ERROR */ INTERNAL_SERVER_ERROR("예상치 못한 서버 에러가 발생했습니다."), - BAD_GATEWAY_EXCEPTION("일시적인 에러가 발생하였습니다.\n잠시 후 다시 시도해주세요!"); + BAD_GATEWAY_EXCEPTION("일시적인 에러가 발생하였습니다.\n잠시 후 다시 시도해주세요!"), + ; private final String message; From f3f8588e2293d698a7d345c320386891e655bbe3 Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Mon, 22 Jan 2024 21:01:23 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[FIX]=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20API=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/Impl/AuthServiceImpl.java | 4 +- .../member/repository/MemberRepository.java | 8 +- .../jwt/JwtAuthenticationEntryPoint.java | 13 +++- .../config/jwt/JwtAuthenticationFilter.java | 75 +++++++++++++------ .../common/config/jwt/JwtTokenProvider.java | 22 ++++-- 5 files changed, 87 insertions(+), 35 deletions(-) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java index 18b4ff2..647c533 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java @@ -69,9 +69,7 @@ public AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchA // socialId를 통해서 등록된 유저 찾기 Member signedMember = memberRepository.findMemberBySocialIdOrThrow(socialData.getId()); - Authentication authentication = new UserAuthentication(signedMember.getId(), null, null); - - String accessToken = jwtTokenProvider.generateAccessToken(authentication); + String accessToken = jwtTokenProvider.generateAccessToken(signedMember.getId()); return AuthResponseDto.of(signedMember.getNickname(), signedMember.getId(), accessToken, signedMember.getRefreshToken()); diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java index 8c63c37..b2ad88f 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java @@ -3,6 +3,7 @@ import com.example.growthookserver.api.member.domain.Member; import com.example.growthookserver.common.exception.BadRequestException; import com.example.growthookserver.common.exception.NotFoundException; +import com.example.growthookserver.common.exception.UnAuthorizedException; import com.example.growthookserver.common.response.ErrorStatus; import org.springframework.data.jpa.repository.JpaRepository; @@ -12,7 +13,7 @@ public interface MemberRepository extends JpaRepository { Optional findMemberById(Long id); boolean existsBySocialId(String socialId); - Optional findByIdAndRefreshToken(Long memberId, String refreshToken); + Optional findByRefreshToken(String refreshToken); Optional findBySocialId(String socialId); @@ -25,4 +26,9 @@ default Member findMemberBySocialIdOrThrow(String socialId) { return findBySocialId(socialId) .orElseThrow(() -> new BadRequestException(ErrorStatus.INVALID_MEMBER.getMessage())); } + + default Member findByRefreshTokenOrThrow(String refreshToken) { + return findByRefreshToken(refreshToken) + .orElseThrow(() -> new UnAuthorizedException(ErrorStatus.INVALID_MEMBER.getMessage())); + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java index 01869f6..fa0ee8b 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java @@ -1,8 +1,12 @@ package com.example.growthookserver.common.config.jwt; +import com.example.growthookserver.common.response.ApiResponse; +import com.example.growthookserver.common.response.ErrorStatus; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @@ -10,16 +14,21 @@ @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + private final ObjectMapper mapper = new ObjectMapper(); + @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { - setResponse(response); + setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.UNAUTHORIZED_TOKEN); } - public void setResponse(HttpServletResponse response) throws IOException { + public void setResponse(HttpServletResponse response, HttpStatus statusCode, ErrorStatus status) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + ApiResponse apiResponse = ApiResponse.fail(statusCode.value(), status.getMessage()); + response.getWriter().println(mapper.writeValueAsString(apiResponse)); } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java index b8beb7f..217c0a8 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java @@ -1,9 +1,8 @@ package com.example.growthookserver.common.config.jwt; - -import static com.example.growthookserver.common.config.jwt.JwtExceptionType.VALID_JWT_TOKEN; - +import com.example.growthookserver.common.exception.UnAuthorizedException; import com.example.growthookserver.common.response.ErrorStatus; +import io.jsonwebtoken.Claims; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -11,9 +10,9 @@ import java.io.IOException; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.AuthenticationException; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -24,28 +23,60 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private static final String ISSUE_TOKEN_API_URL = "/api/v1/auth/token"; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - String accessToken = jwtTokenProvider.resolveToken(request); - - if (accessToken != null) { - // 토큰 검증 - if (jwtTokenProvider.validateToken(accessToken) - == VALID_JWT_TOKEN) { // 토큰이 존재하고 유효한 토큰일 때만 - Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken); - UserAuthentication authentication = new UserAuthentication(userId, null, - null); //사용자 객체 생성 - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( - request)); // request 정보로 사용자 객체 디테일 설정 - SecurityContextHolder.getContext().setAuthentication(authentication); - } else { - jwtAuthenticationEntryPoint.commence(request, response, - new AuthenticationException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()) { - }); - return; + try { + String accessToken = jwtTokenProvider.resolveToken(request); + if (ISSUE_TOKEN_API_URL.equals(request.getRequestURI())) { + String refreshToken = jwtTokenProvider.resolveRefreshToken(request); + + if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.EMPTY_JWT || jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.EMPTY_JWT) { + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.BAD_REQUEST, ErrorStatus.NO_TOKEN); + return; + } else if (jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.EXPIRED_JWT_TOKEN) { + if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.EXPIRED_JWT_TOKEN) { + // access, refresh 둘 다 만료 + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.SIGNIN_REQUIRED); + return; + } else if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.VALID_JWT_TOKEN) { + // 토큰 재발급 + Long memberId = jwtTokenProvider.validateMemberRefreshToken(refreshToken); + + String newAccessToken = jwtTokenProvider.generateAccessToken(memberId); + + setAuthentication(newAccessToken); + request.setAttribute("newAccessToken", newAccessToken); + } + } else if (jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.VALID_JWT_TOKEN) { + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.VALID_ACCESS_TOKEN); + return; + } else { + throw new UnAuthorizedException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()); + } } + else { + JwtExceptionType jwtException = jwtTokenProvider.validateToken(accessToken); + + if (accessToken != null) { + // 토큰 검증 + if (jwtException == JwtExceptionType.VALID_JWT_TOKEN) { + setAuthentication(accessToken); + } + } + } + } catch (Exception e) { + throw new UnAuthorizedException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()); } + chain.doFilter(request, response); } + + private void setAuthentication(String token) { + Claims claims = jwtTokenProvider.getAccessTokenPayload(token); + Authentication authentication = new UserAuthentication(Long.valueOf(String.valueOf(claims.get("id"))), null, null); + SecurityContextHolder.getContext().setAuthentication(authentication); + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java index 7c3d867..42f5490 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java @@ -1,5 +1,7 @@ package com.example.growthookserver.common.config.jwt; +import com.example.growthookserver.api.member.domain.Member; +import com.example.growthookserver.api.member.repository.MemberRepository; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Header; @@ -23,6 +25,8 @@ @RequiredArgsConstructor public class JwtTokenProvider { + private final MemberRepository memberRepository; + @Value("${jwt.secret}") private String secretKey; @@ -33,9 +37,9 @@ public class JwtTokenProvider { private Long refreshTokenExpireLength; private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String REFRESH_AUTHORIZATION_HEADER = "Refresh"; + private static final String REFRESH_AUTHORIZATION_HEADER = "refreshToken"; - public String generateAccessToken(Authentication authentication) { + public String generateAccessToken(Long memberId) { Date now = new Date(); Date expiration = new Date(now.getTime() + accessTokenExpireLength); @@ -43,7 +47,7 @@ public String generateAccessToken(Authentication authentication) { .setIssuedAt(now) .setExpiration(expiration); - claims.put("id", authentication.getPrincipal()); + claims.put("id", memberId); return Jwts.builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) @@ -63,10 +67,9 @@ public String generateRefreshToken() { .compact(); } - public Integer getAccessTokenPayload(String token) { - return Integer.parseInt( - Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) - .getBody().get("id").toString()); + public Claims getAccessTokenPayload(String token) { + return Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody(); } public String resolveToken(HttpServletRequest request) { @@ -117,4 +120,9 @@ private Key getSignKey() { byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); return new SecretKeySpec(keyBytes, "HmacSHA256"); } + + public Long validateMemberRefreshToken(String refreshToken) { + Member member = memberRepository.findByRefreshTokenOrThrow(refreshToken); + return member.getId(); + } } From afcccef8a33d629201ae72c3a8b326d2e01e5a9c Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Tue, 23 Jan 2024 04:03:57 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[FIX]=20=ED=9A=8C=EC=9B=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C,=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20url=EB=8F=84?= =?UTF-8?q?=20=EB=82=B4=EB=A0=A4=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/member/dto/response/MemberDetailGetResponseDto.java | 1 + .../growthookserver/api/member/service/MemberServiceImpl.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/dto/response/MemberDetailGetResponseDto.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/dto/response/MemberDetailGetResponseDto.java index 5e413f7..777658e 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/dto/response/MemberDetailGetResponseDto.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/dto/response/MemberDetailGetResponseDto.java @@ -11,4 +11,5 @@ public class MemberDetailGetResponseDto { private String nickname; private String email; + private String profileImage; } diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/service/MemberServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/service/MemberServiceImpl.java index a657370..311e087 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/service/MemberServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/service/MemberServiceImpl.java @@ -19,7 +19,8 @@ public class MemberServiceImpl implements MemberService{ @Override public MemberDetailGetResponseDto getMemberProfile(Long memberId) { Member member = memberRepository.findMemberByIdOrThrow(memberId); - return MemberDetailGetResponseDto.of(member.getNickname(), member.getEmail()); + return MemberDetailGetResponseDto.of(member.getNickname(), member.getEmail(), + member.getProfileImage()); } @Override