From 4245d5e7df86bce54d3e8b7de911479664ed9079 Mon Sep 17 00:00:00 2001 From: Jeongmo Seo Date: Sat, 16 Aug 2025 15:11:52 +0900 Subject: [PATCH] =?UTF-8?q?:bug:=20fix:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/AuthCommandServiceImpl.java | 16 ++----- .../member/controller/MemberController.java | 6 ++- .../service/command/MemberCommandService.java | 4 +- .../command/MemberCommandServiceImpl.java | 11 ++++- .../security/handler/CustomLogoutHandler.java | 47 +++++++++++++++++++ 5 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/withtime/be/withtimebe/global/security/handler/CustomLogoutHandler.java diff --git a/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/AuthCommandServiceImpl.java b/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/AuthCommandServiceImpl.java index d06c43e..4b2372d 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/AuthCommandServiceImpl.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/auth/service/command/AuthCommandServiceImpl.java @@ -20,6 +20,7 @@ import org.withtime.be.withtimebe.global.error.exception.*; import org.withtime.be.withtimebe.global.security.constants.AuthenticationConstants; import org.withtime.be.withtimebe.global.security.domain.CustomUserDetails; +import org.withtime.be.withtimebe.global.security.handler.CustomLogoutHandler; import org.withtime.be.withtimebe.global.util.CookieUtil; @Service @@ -36,6 +37,7 @@ public class AuthCommandServiceImpl implements AuthCommandService { private final TokenQueryService tokenQueryService; private final TokenStorageQueryService tokenStorageQueryService; private final EmailVerificationCodeStorageQueryService emailVerificationCodeStorageQueryService; + private final CustomLogoutHandler customLogoutHandler; @Override public void signUp(AuthRequestDTO.SignUp request) { @@ -69,19 +71,7 @@ public void reissueToken(HttpServletRequest request, HttpServletResponse respons @Override public void logout(HttpServletRequest request, HttpServletResponse response) { - String accessToken = getAccessToken(request); - String refreshToken = getRefreshToken(request); - - tokenStorageCommandService.addBlackList(accessToken); - tokenStorageCommandService.addBlackList(refreshToken); - - // 쿠키의 Refresh Token이 다른 경우를 대비해 Redis Refresh도 Black list 처리 - Long userId = getUserId(refreshToken); - tokenStorageCommandService.addBlackList(tokenStorageQueryService.getRefreshToken(userId)); - tokenStorageCommandService.deleteRefreshToken(userId); - - CookieUtil.deleteCookie(request, response, AuthenticationConstants.ACCESS_TOKEN_NAME); - CookieUtil.deleteCookie(request, response, AuthenticationConstants.REFRESH_TOKEN_NAME); + customLogoutHandler.logout(request, response); } @Override diff --git a/src/main/java/org/withtime/be/withtimebe/domain/member/controller/MemberController.java b/src/main/java/org/withtime/be/withtimebe/domain/member/controller/MemberController.java index 2e727c2..a8322b3 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/member/controller/MemberController.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/member/controller/MemberController.java @@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.namul.api.payload.response.DefaultResponse; @@ -79,8 +81,8 @@ public DefaultResponse changeInfo(@AuthenticatedMe @Operation(summary = "회원 탈퇴하기 API by 요시", description = "로그인된 토큰을 이용하여 회원 탈퇴하는 API") @ApiResponse(responseCode = "204", description = "회원 탈퇴 성공 (soft delete)") @DeleteMapping - public DefaultResponse deleteMember(@AuthenticatedMember Member member) { - memberCommandService.deleteMember(member.getId()); + public DefaultResponse deleteMember(HttpServletRequest request, HttpServletResponse response, @AuthenticatedMember Member member) { + memberCommandService.deleteMember(request, response, member.getId()); return DefaultResponse.noContent(); } diff --git a/src/main/java/org/withtime/be/withtimebe/domain/member/service/command/MemberCommandService.java b/src/main/java/org/withtime/be/withtimebe/domain/member/service/command/MemberCommandService.java index 560484c..204dd7a 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/member/service/command/MemberCommandService.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/member/service/command/MemberCommandService.java @@ -1,5 +1,7 @@ package org.withtime.be.withtimebe.domain.member.service.command; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.withtime.be.withtimebe.domain.member.dto.MemberRequestDTO; import org.withtime.be.withtimebe.domain.member.entity.Member; @@ -8,5 +10,5 @@ public interface MemberCommandService { void changePassword(String email, String password); Member changeInfo(Long memberId, MemberRequestDTO.ChangeInfo request); void addPoint(Long memberId, Integer point); - void deleteMember(Long memberId); + void deleteMember(HttpServletRequest request, HttpServletResponse response, Long memberId); } diff --git a/src/main/java/org/withtime/be/withtimebe/domain/member/service/command/MemberCommandServiceImpl.java b/src/main/java/org/withtime/be/withtimebe/domain/member/service/command/MemberCommandServiceImpl.java index c58ca70..3a8c2d8 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/member/service/command/MemberCommandServiceImpl.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/member/service/command/MemberCommandServiceImpl.java @@ -1,10 +1,13 @@ package org.withtime.be.withtimebe.domain.member.service.command; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.namul.api.payload.error.exception.ServerApplicationException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.withtime.be.withtimebe.domain.auth.service.command.AuthCommandService; import org.withtime.be.withtimebe.domain.member.dto.MemberRequestDTO; import org.withtime.be.withtimebe.domain.member.entity.Member; import org.withtime.be.withtimebe.domain.member.repository.MemberRepository; @@ -12,6 +15,7 @@ import org.withtime.be.withtimebe.global.error.code.MemberErrorCode; import org.withtime.be.withtimebe.global.error.exception.AuthException; import org.withtime.be.withtimebe.global.error.exception.MemberException; +import org.withtime.be.withtimebe.global.security.handler.CustomLogoutHandler; @Service @RequiredArgsConstructor @@ -20,6 +24,7 @@ public class MemberCommandServiceImpl implements MemberCommandService { private final PasswordEncoder passwordEncoder; private final MemberRepository memberRepository; + private final CustomLogoutHandler customLogoutHandler; @Override @@ -52,8 +57,10 @@ public void addPoint(Long memberId, Integer point) { new MemberException(MemberErrorCode.NOT_FOUND)); member.addPoint(point); } - - public void deleteMember(Long memberId) { + + @Override + public void deleteMember(HttpServletRequest request, HttpServletResponse response, Long memberId) { + customLogoutHandler.logout(request, response); memberRepository.deleteById(memberId); } diff --git a/src/main/java/org/withtime/be/withtimebe/global/security/handler/CustomLogoutHandler.java b/src/main/java/org/withtime/be/withtimebe/global/security/handler/CustomLogoutHandler.java new file mode 100644 index 0000000..844b82a --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/security/handler/CustomLogoutHandler.java @@ -0,0 +1,47 @@ +package org.withtime.be.withtimebe.global.security.handler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.withtime.be.withtimebe.domain.auth.service.command.TokenStorageCommandService; +import org.withtime.be.withtimebe.domain.auth.service.query.TokenQueryService; +import org.withtime.be.withtimebe.domain.auth.service.query.TokenStorageQueryService; +import org.withtime.be.withtimebe.global.security.constants.AuthenticationConstants; +import org.withtime.be.withtimebe.global.util.CookieUtil; + +@Component +@RequiredArgsConstructor +public class CustomLogoutHandler { + + private final TokenStorageCommandService tokenStorageCommandService; + private final TokenStorageQueryService tokenStorageQueryService; + private final TokenQueryService tokenQueryService; + + public void logout(HttpServletRequest request, HttpServletResponse response) { + String accessToken = getAccessToken(request); + String refreshToken = getRefreshToken(request); + + tokenStorageCommandService.addBlackList(accessToken); + tokenStorageCommandService.addBlackList(refreshToken); + + // 쿠키의 Refresh Token이 다른 경우를 대비해 Redis Refresh도 Black list 처리 + Long userId = getUserId(refreshToken); + tokenStorageCommandService.addBlackList(tokenStorageQueryService.getRefreshToken(userId)); + tokenStorageCommandService.deleteRefreshToken(userId); + + CookieUtil.deleteCookie(request, response, AuthenticationConstants.ACCESS_TOKEN_NAME); + CookieUtil.deleteCookie(request, response, AuthenticationConstants.REFRESH_TOKEN_NAME); + } + + private Long getUserId(String token) { + return tokenQueryService.getUserId(token); + } + + private String getAccessToken(HttpServletRequest request) { + return CookieUtil.getCookie(request, AuthenticationConstants.ACCESS_TOKEN_NAME); + } + + private String getRefreshToken(HttpServletRequest request) { + return CookieUtil.getCookie(request, AuthenticationConstants.REFRESH_TOKEN_NAME); + }}