From 98c5138622b4861a8ba3306bde331a8902b5f25b Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 00:58:31 +0900 Subject: [PATCH 01/12] =?UTF-8?q?[feat]=20:=20RestTemplateConfig=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/RestTemplateConfig.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/com/dnd/gongmuin/common/config/RestTemplateConfig.java diff --git a/src/main/java/com/dnd/gongmuin/common/config/RestTemplateConfig.java b/src/main/java/com/dnd/gongmuin/common/config/RestTemplateConfig.java new file mode 100644 index 00000000..07fddbf7 --- /dev/null +++ b/src/main/java/com/dnd/gongmuin/common/config/RestTemplateConfig.java @@ -0,0 +1,21 @@ +package com.dnd.gongmuin.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + @Bean + public RestTemplate restTemplate() { + RestTemplate restTemplate = new RestTemplate(); + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + requestFactory.setConnectTimeout(5000); + requestFactory.setReadTimeout(5000); + + restTemplate.setRequestFactory(requestFactory); + + return restTemplate; + } +} From 16c1b2327ec7a74d42aa41b8a3dfe8970e4153b3 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 00:59:22 +0900 Subject: [PATCH 02/12] =?UTF-8?q?[feat]=20:=20OAuth2AccessToken=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/gongmuin/security/oauth2/KakaoResponse.java | 9 ++++++++- .../com/dnd/gongmuin/security/oauth2/NaverResponse.java | 9 ++++++++- .../com/dnd/gongmuin/security/oauth2/Oauth2Response.java | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/security/oauth2/KakaoResponse.java b/src/main/java/com/dnd/gongmuin/security/oauth2/KakaoResponse.java index 4cf40fea..ba082855 100644 --- a/src/main/java/com/dnd/gongmuin/security/oauth2/KakaoResponse.java +++ b/src/main/java/com/dnd/gongmuin/security/oauth2/KakaoResponse.java @@ -8,10 +8,12 @@ public class KakaoResponse implements Oauth2Response { private final Map attribute; private final Long id; + private final String oauth2AccessToken; - public KakaoResponse(Map attribute) { + public KakaoResponse(Map attribute, String oauth2AccessToken) { this.attribute = (Map)attribute.get("kakao_account"); this.id = (Long)attribute.get("id"); + this.oauth2AccessToken = oauth2AccessToken; } @Override @@ -43,4 +45,9 @@ public String createSocialEmail() { ); } + @Override + public String getOauth2AccessToken() { + return this.oauth2AccessToken; + } + } diff --git a/src/main/java/com/dnd/gongmuin/security/oauth2/NaverResponse.java b/src/main/java/com/dnd/gongmuin/security/oauth2/NaverResponse.java index 79b7df2f..5437833e 100644 --- a/src/main/java/com/dnd/gongmuin/security/oauth2/NaverResponse.java +++ b/src/main/java/com/dnd/gongmuin/security/oauth2/NaverResponse.java @@ -7,9 +7,11 @@ public class NaverResponse implements Oauth2Response { private final Map attribute; + private final String oauth2AccessToken; - public NaverResponse(Map attribute) { + public NaverResponse(Map attribute, String oauth2AccessToken) { this.attribute = (Map)attribute.get("response"); + this.oauth2AccessToken = oauth2AccessToken; } @Override @@ -40,4 +42,9 @@ public String createSocialEmail() { this.getEmail() ); } + + @Override + public String getOauth2AccessToken() { + return this.oauth2AccessToken; + } } diff --git a/src/main/java/com/dnd/gongmuin/security/oauth2/Oauth2Response.java b/src/main/java/com/dnd/gongmuin/security/oauth2/Oauth2Response.java index 745828d5..3be2eafb 100644 --- a/src/main/java/com/dnd/gongmuin/security/oauth2/Oauth2Response.java +++ b/src/main/java/com/dnd/gongmuin/security/oauth2/Oauth2Response.java @@ -11,4 +11,5 @@ public interface Oauth2Response { String createSocialEmail(); + String getOauth2AccessToken(); } From 7bcd7f96411010fc5001c2c21d922b9b87af7c48 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:00:38 +0900 Subject: [PATCH 03/12] =?UTF-8?q?[feat]=20:=20oauth2AccessToken=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberService.java | 25 ++++++++++++++++++- .../service/CustomOauth2UserService.java | 7 ++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/member/service/MemberService.java b/src/main/java/com/dnd/gongmuin/member/service/MemberService.java index 82dd5075..ab3afe3f 100644 --- a/src/main/java/com/dnd/gongmuin/member/service/MemberService.java +++ b/src/main/java/com/dnd/gongmuin/member/service/MemberService.java @@ -1,5 +1,7 @@ package com.dnd.gongmuin.member.service; +import java.time.Duration; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -23,6 +25,7 @@ import com.dnd.gongmuin.member.dto.response.QuestionPostsResponse; import com.dnd.gongmuin.member.exception.MemberErrorCode; import com.dnd.gongmuin.member.repository.MemberRepository; +import com.dnd.gongmuin.redis.util.RedisUtil; import com.dnd.gongmuin.security.oauth2.Oauth2Response; import lombok.RequiredArgsConstructor; @@ -31,12 +34,16 @@ @RequiredArgsConstructor public class MemberService { + private static final long ACCESS_TOKEN_EXPIRATION = 3600 * 1000; private final MemberRepository memberRepository; + private final RedisUtil redisUtil; public Member saveOrUpdate(Oauth2Response oauth2Response) { Member member = memberRepository.findBySocialEmail(oauth2Response.createSocialEmail()) .map(m -> { m.updateSocialEmail(oauth2Response.createSocialEmail()); + deleteExistingOauthAccessToken(m); + saveOauth2AccessToken(oauth2Response, m); return m; }) .orElseGet(() -> createMemberFromOauth2Response(oauth2Response)); @@ -44,13 +51,29 @@ public Member saveOrUpdate(Oauth2Response oauth2Response) { return memberRepository.save(member); } + private void saveOauth2AccessToken(Oauth2Response oauth2Response, Member m) { + redisUtil.setValues( + "AT(oauth):" + m.getSocialEmail(), + oauth2Response.getOauth2AccessToken(), + Duration.ofMillis(ACCESS_TOKEN_EXPIRATION) + ); + } + + private void deleteExistingOauthAccessToken(Member m) { + if (redisUtil.getValues("AT(oauth2):" + m.getSocialEmail()) != null) { + redisUtil.deleteValues("AT(oauth2):" + m.getSocialEmail()); + } + } + public Provider parseProviderFromSocialEmail(Member member) { String socialEmail = member.getSocialEmail(); return Provider.fromSocialEmail(socialEmail); } private Member createMemberFromOauth2Response(Oauth2Response oauth2Response) { - return Member.of(oauth2Response.getName(), oauth2Response.createSocialEmail(), 10000, "ROLE_GUEST"); + Member member = Member.of(oauth2Response.getName(), oauth2Response.createSocialEmail(), 10000, "ROLE_GUEST"); + saveOauth2AccessToken(oauth2Response, member); + return member; } public Member getMemberBySocialEmail(String socialEmail) { diff --git a/src/main/java/com/dnd/gongmuin/security/service/CustomOauth2UserService.java b/src/main/java/com/dnd/gongmuin/security/service/CustomOauth2UserService.java index c370c2d5..9c907c35 100644 --- a/src/main/java/com/dnd/gongmuin/security/service/CustomOauth2UserService.java +++ b/src/main/java/com/dnd/gongmuin/security/service/CustomOauth2UserService.java @@ -34,13 +34,15 @@ public class CustomOauth2UserService extends DefaultOAuth2UserService { public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User oAuth2User = super.loadUser(userRequest); + String oauth2AccessToken = userRequest.getAccessToken().getTokenValue(); + String registrationId = userRequest.getClientRegistration().getRegistrationId(); Oauth2Response oauth2Response = null; if (Objects.equals(registrationId, KAKAO.getLabel())) { - oauth2Response = new KakaoResponse(oAuth2User.getAttributes()); + oauth2Response = new KakaoResponse(oAuth2User.getAttributes(), oauth2AccessToken); } else if (Objects.equals(registrationId, NAVER.getLabel())) { - oauth2Response = new NaverResponse(oAuth2User.getAttributes()); + oauth2Response = new NaverResponse(oAuth2User.getAttributes(), oauth2AccessToken); } else { throw new OAuth2AuthenticationException( new OAuth2Error(UNSUPPORTED_SOCIAL_LOGIN.getCode()), @@ -57,5 +59,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic ); return new CustomOauth2User(authInfo); } + } From c1cd7cf0b4c09019e170b1fc96b43b72c630282e Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:01:54 +0900 Subject: [PATCH 04/12] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnd/gongmuin/auth/controller/AuthController.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java b/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java index 5a13c537..1ae2e0c9 100644 --- a/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java +++ b/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java @@ -12,6 +12,7 @@ import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; +import com.dnd.gongmuin.auth.dto.response.MemberDeletionResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.SignUpResponse; import com.dnd.gongmuin.auth.dto.response.TempSignResponse; @@ -93,5 +94,16 @@ public ResponseEntity reissue(HttpServletRequest request, HttpS return ResponseEntity.ok(reissueResponse); } + + @Operation(summary = "회원탈퇴 API", description = "회원 탈퇴한다.") + @ApiResponse(useReturnTypeSchema = true) + @PostMapping("/delete") + public ResponseEntity deleteMember( + HttpServletRequest request + ) { + MemberDeletionResponse memberDeletionResponse = authService.deleteMember(request); + + return ResponseEntity.ok(memberDeletionResponse); + } } From 295c46aa12dff1252dd6629aaef2a9f62190f865 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:02:06 +0900 Subject: [PATCH 05/12] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=9D=91=EB=8B=B5=20DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gongmuin/auth/dto/response/MemberDeletionResponse.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java diff --git a/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java b/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java new file mode 100644 index 00000000..b14e58c7 --- /dev/null +++ b/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java @@ -0,0 +1,7 @@ +package com.dnd.gongmuin.auth.dto.response; + +public record MemberDeletionResponse( + + Long memberId +) { +} From e2b0500b77b78b7e07472ead159273ec795e01da Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:02:18 +0900 Subject: [PATCH 06/12] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/gongmuin/member/exception/MemberErrorCode.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/dnd/gongmuin/member/exception/MemberErrorCode.java b/src/main/java/com/dnd/gongmuin/member/exception/MemberErrorCode.java index a8d07ea2..eeab7457 100644 --- a/src/main/java/com/dnd/gongmuin/member/exception/MemberErrorCode.java +++ b/src/main/java/com/dnd/gongmuin/member/exception/MemberErrorCode.java @@ -16,7 +16,8 @@ public enum MemberErrorCode implements ErrorCode { UPDATE_PROFILE_FAILED("프로필 수정에 실패했습니다.", "MEMBER_005"), QUESTION_POSTS_BY_MEMBER_FAILED("마이페이지 게시글 목록을 불러오는데 실패했습니다", "MEMBER_006"), NOT_FOUND_JOB_GROUP("직군을 올바르게 입력해주세요.", "MEMBER_007"), - NOT_FOUND_JOB_CATEGORY("직렬을 올바르게 입력해주세요.", "MEMBER_008"); + NOT_FOUND_JOB_CATEGORY("직렬을 올바르게 입력해주세요.", "MEMBER_008"), + DELETE_FAILED("회원탈퇴를 실패했습니다.", "MEMBER_009"); private final String message; private final String code; From 5b5c3376f50414eecdea67eff78283fc812ebe93 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:02:38 +0900 Subject: [PATCH 07/12] =?UTF-8?q?[feat]=20:=20=ED=9A=8C=EC=9B=90=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gongmuin/auth/service/AuthService.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java index be7491c3..49bffd11 100644 --- a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java +++ b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java @@ -13,6 +13,7 @@ import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; +import com.dnd.gongmuin.auth.dto.response.MemberDeletionResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.SignUpResponse; import com.dnd.gongmuin.auth.dto.response.TempSignResponse; @@ -30,6 +31,7 @@ import com.dnd.gongmuin.security.jwt.util.TokenProvider; import com.dnd.gongmuin.security.oauth2.AuthInfo; import com.dnd.gongmuin.security.oauth2.CustomOauth2User; +import com.dnd.gongmuin.security.service.OAuth2UnlinkService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -40,10 +42,12 @@ public class AuthService { private static final String LOGOUT = "logout"; + private static final String DELETE = "delete"; private final TokenProvider tokenProvider; private final MemberRepository memberRepository; private final CookieUtil cookieUtil; private final RedisUtil redisUtil; + private final OAuth2UnlinkService oAuth2UnlinkService; @Transactional public TempSignResponse tempSignUp(TempSignUpRequest tempSignUpRequest, HttpServletResponse response) { @@ -181,4 +185,41 @@ private void updateAdditionalInfo(AdditionalInfoRequest request, Member findMemb ); } + @Transactional + public MemberDeletionResponse deleteMember(HttpServletRequest request) { + String accessToken = cookieUtil.getCookieValue(request); + + if (!tokenProvider.validateToken(accessToken, new Date())) { + throw new ValidationException(AuthErrorCode.UNAUTHORIZED_TOKEN); + } + + Authentication authentication = tokenProvider.getAuthentication(accessToken); + Member member = (Member)authentication.getPrincipal(); + + // RefreshToken 삭제 + if (!Objects.isNull(redisUtil.getValues("RT:" + member.getSocialEmail()))) { + redisUtil.deleteValues("RT:" + member.getSocialEmail()); + } + + // 현재 발급 되어 있는 AccessToken 블랙리스트 등록 + Long expiration = tokenProvider.getExpiration(accessToken, new Date()); + redisUtil.setValues(accessToken, DELETE, Duration.ofMillis(expiration)); + + // AccessToken 블랙리스트 등록 여부 검증 + String values = redisUtil.getValues(accessToken); + if (!Objects.equals(values, DELETE)) { + throw new NotFoundException(MemberErrorCode.DELETE_FAILED); + } + + // TODO: SOFT DELETE + + // oauth2 서비스 연결 끊기 + oAuth2UnlinkService.unlink(member.getSocialEmail()); + + // oauth2 access 토큰 삭제 + if (redisUtil.getValues("AT(oauth):" + member.getSocialEmail()) != null) { + redisUtil.deleteValues("AT(oauth):" + member.getSocialEmail()); + } + return new MemberDeletionResponse(member.getId()); + } } From c949f46d85bcf3fcff0dcd996e746198e181ba6c Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:03:08 +0900 Subject: [PATCH 08/12] =?UTF-8?q?[feat]=20:=20Oauth2=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EB=81=8A=EA=B8=B0=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20Service=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/service/OAuth2UnlinkService.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java diff --git a/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java b/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java new file mode 100644 index 00000000..8b03720c --- /dev/null +++ b/src/main/java/com/dnd/gongmuin/security/service/OAuth2UnlinkService.java @@ -0,0 +1,105 @@ +package com.dnd.gongmuin.security.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import com.dnd.gongmuin.common.exception.runtime.ValidationException; +import com.dnd.gongmuin.redis.util.RedisUtil; +import com.dnd.gongmuin.security.exception.OAuth2ErrorCode; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class OAuth2UnlinkService { + + private static final String KAKAO_URL = "https://kapi.kakao.com/v1/user/unlink"; + private static final String NAVER_URL = "https://nid.naver.com/oauth2.0/token"; + private final RestTemplate restTemplate; + private final RedisUtil redisUtil; + @Value("${spring.security.oauth2.client.registration.naver.client-id}") + private String NAVER_CLIENT_ID; + @Value("${spring.security.oauth2.client.registration.naver.client-secret}") + private String NAVER_CLIENT_SECRET; + + public void unlink(String provider) { + if (provider.startsWith("kakao")) { + kakaoUnlink(provider); + } else if (provider.startsWith("naver")) { + naverUnlink(provider); + } else { + throw new ValidationException(OAuth2ErrorCode.INVALID_REQUEST); + } + } + + public void kakaoUnlink(String provider) { + String accessToken = redisUtil.getValues("AT(oauth):" + provider); + // oauth2 토큰이 만료 시 재 로그인 + if (accessToken == null) { + throw new ValidationException(OAuth2ErrorCode.EXPIRED_AUTH_TOKEN); + } + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + HttpEntity entity = new HttpEntity<>("", headers); + ResponseEntity responseEntity = restTemplate.exchange( + KAKAO_URL, + HttpMethod.POST, + entity, + String.class + ); + + if (responseEntity.getBody().isEmpty()) { + throw new ValidationException(OAuth2ErrorCode.INTERNAL_SERVER_ERROR); + } + } + + public void naverUnlink(String provider) { + String accessToken = redisUtil.getValues("AT(oauth):" + provider); + + // oauth2 토큰이 만료 시 재 로그인 + if (accessToken == null) { + throw new ValidationException(OAuth2ErrorCode.EXPIRED_AUTH_TOKEN); + } + + String url = NAVER_URL + + "?service_provider=NAVER" + + "&grant_type=delete" + + "&client_id=" + + NAVER_CLIENT_ID + + "&client_secret=" + + NAVER_CLIENT_SECRET + + "&access_token=" + + accessToken; + + NaverUnlinkResponse response = restTemplate.getForObject(url, NaverUnlinkResponse.class); + + if (response != null && !"success".equalsIgnoreCase(response.getResult())) { + throw new ValidationException(OAuth2ErrorCode.INTERNAL_SERVER_ERROR); + } + } + + /** + * 네이버 응답 데이터 + */ + @Getter + public static class NaverUnlinkResponse { + private final String accessToken; + private final String result; + + @JsonCreator + public NaverUnlinkResponse( + @JsonProperty("access_token") String accessToken, + @JsonProperty("result") String result) { + this.accessToken = accessToken; + this.result = result; + } + } +} From 191cb3090b43ab282c01b3b7575531a31f8e466f Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:03:28 +0900 Subject: [PATCH 09/12] =?UTF-8?q?[feat]=20:=20OAuth2=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/exception/OAuth2ErrorCode.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/com/dnd/gongmuin/security/exception/OAuth2ErrorCode.java diff --git a/src/main/java/com/dnd/gongmuin/security/exception/OAuth2ErrorCode.java b/src/main/java/com/dnd/gongmuin/security/exception/OAuth2ErrorCode.java new file mode 100644 index 00000000..717274ee --- /dev/null +++ b/src/main/java/com/dnd/gongmuin/security/exception/OAuth2ErrorCode.java @@ -0,0 +1,17 @@ +package com.dnd.gongmuin.security.exception; + +import com.dnd.gongmuin.common.exception.ErrorCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum OAuth2ErrorCode implements ErrorCode { + + INVALID_REQUEST("유효하지 않은 탈퇴 요청입니다.", "OAUTH2_001"), + EXPIRED_AUTH_TOKEN("만료된 OAuth2 토큰입니다.", "OAUTH2_002"), + INTERNAL_SERVER_ERROR("OAuth 서버 에러 발생입니다.", "OAUTH2_003"); + private final String message; + private final String code; +} From 75c67f654153fd956040ff58390f6c71a68c2de6 Mon Sep 17 00:00:00 2001 From: dudxo Date: Wed, 25 Sep 2024 01:04:00 +0900 Subject: [PATCH 10/12] =?UTF-8?q?[refactor}=20:=20=EB=B8=94=EB=9E=99?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=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 --- .../security/jwt/util/TokenAuthenticationFilter.java | 2 +- .../com/dnd/gongmuin/security/jwt/util/TokenProvider.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenAuthenticationFilter.java b/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenAuthenticationFilter.java index eea059b5..464ab581 100644 --- a/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenAuthenticationFilter.java +++ b/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenAuthenticationFilter.java @@ -33,7 +33,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (tokenProvider.validateToken(accessToken, new Date())) { // accessToken logout 여부 확인 - if (tokenProvider.verifyLogout(accessToken)) { + if (tokenProvider.verifyBlackList(accessToken)) { saveAuthentication(accessToken); } } diff --git a/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenProvider.java b/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenProvider.java index 8bdb8966..fd4e25bc 100644 --- a/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenProvider.java +++ b/src/main/java/com/dnd/gongmuin/security/jwt/util/TokenProvider.java @@ -1,10 +1,10 @@ package com.dnd.gongmuin.security.jwt.util; import java.time.Duration; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import javax.crypto.SecretKey; @@ -40,6 +40,7 @@ public class TokenProvider { private static final String ROLE_KEY = "ROLE"; + private static final String[] BLACKLIST = new String[] {"false", "delete"}; private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 90L; private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24L; private final MemberRepository memberRepository; @@ -136,9 +137,9 @@ public Long getExpiration(String token, Date date) { return (expiration.getTime() - date.getTime()); } - public boolean verifyLogout(String accessToken) { + public boolean verifyBlackList(String accessToken) { String value = redisUtil.getValues(accessToken); - return Objects.equals("false", value); + return Arrays.asList(BLACKLIST).contains(value); } } From dee41ab9d9c76febd13fc0618e0f9f3bdc85aa1e Mon Sep 17 00:00:00 2001 From: dudxo Date: Fri, 27 Sep 2024 16:53:53 +0900 Subject: [PATCH 11/12] =?UTF-8?q?[rename]=20:=20DTO=20=EC=BB=A8=EB=B2=A4?= =?UTF-8?q?=EC=85=98=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dnd/gongmuin/auth/controller/AuthController.java | 8 ++++---- ...berDeletionResponse.java => DeleteMemberResponse.java} | 2 +- .../java/com/dnd/gongmuin/auth/service/AuthService.java | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/com/dnd/gongmuin/auth/dto/response/{MemberDeletionResponse.java => DeleteMemberResponse.java} (63%) diff --git a/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java b/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java index 1ae2e0c9..0c11db55 100644 --- a/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java +++ b/src/main/java/com/dnd/gongmuin/auth/controller/AuthController.java @@ -11,8 +11,8 @@ import com.dnd.gongmuin.auth.dto.request.TempSignInRequest; import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; +import com.dnd.gongmuin.auth.dto.response.DeleteMemberResponse; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; -import com.dnd.gongmuin.auth.dto.response.MemberDeletionResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.SignUpResponse; import com.dnd.gongmuin.auth.dto.response.TempSignResponse; @@ -98,12 +98,12 @@ public ResponseEntity reissue(HttpServletRequest request, HttpS @Operation(summary = "회원탈퇴 API", description = "회원 탈퇴한다.") @ApiResponse(useReturnTypeSchema = true) @PostMapping("/delete") - public ResponseEntity deleteMember( + public ResponseEntity deleteMember( HttpServletRequest request ) { - MemberDeletionResponse memberDeletionResponse = authService.deleteMember(request); + DeleteMemberResponse deleteMemberResponse = authService.deleteMember(request); - return ResponseEntity.ok(memberDeletionResponse); + return ResponseEntity.ok(deleteMemberResponse); } } diff --git a/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java b/src/main/java/com/dnd/gongmuin/auth/dto/response/DeleteMemberResponse.java similarity index 63% rename from src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java rename to src/main/java/com/dnd/gongmuin/auth/dto/response/DeleteMemberResponse.java index b14e58c7..1a661cf5 100644 --- a/src/main/java/com/dnd/gongmuin/auth/dto/response/MemberDeletionResponse.java +++ b/src/main/java/com/dnd/gongmuin/auth/dto/response/DeleteMemberResponse.java @@ -1,6 +1,6 @@ package com.dnd.gongmuin.auth.dto.response; -public record MemberDeletionResponse( +public record DeleteMemberResponse( Long memberId ) { diff --git a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java index 49bffd11..6a802d1f 100644 --- a/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java +++ b/src/main/java/com/dnd/gongmuin/auth/service/AuthService.java @@ -12,8 +12,8 @@ import com.dnd.gongmuin.auth.dto.request.TempSignInRequest; import com.dnd.gongmuin.auth.dto.request.TempSignUpRequest; import com.dnd.gongmuin.auth.dto.request.ValidateNickNameRequest; +import com.dnd.gongmuin.auth.dto.response.DeleteMemberResponse; import com.dnd.gongmuin.auth.dto.response.LogoutResponse; -import com.dnd.gongmuin.auth.dto.response.MemberDeletionResponse; import com.dnd.gongmuin.auth.dto.response.ReissueResponse; import com.dnd.gongmuin.auth.dto.response.SignUpResponse; import com.dnd.gongmuin.auth.dto.response.TempSignResponse; @@ -186,7 +186,7 @@ private void updateAdditionalInfo(AdditionalInfoRequest request, Member findMemb } @Transactional - public MemberDeletionResponse deleteMember(HttpServletRequest request) { + public DeleteMemberResponse deleteMember(HttpServletRequest request) { String accessToken = cookieUtil.getCookieValue(request); if (!tokenProvider.validateToken(accessToken, new Date())) { @@ -220,6 +220,6 @@ public MemberDeletionResponse deleteMember(HttpServletRequest request) { if (redisUtil.getValues("AT(oauth):" + member.getSocialEmail()) != null) { redisUtil.deleteValues("AT(oauth):" + member.getSocialEmail()); } - return new MemberDeletionResponse(member.getId()); + return new DeleteMemberResponse(member.getId()); } } From f0c373445accc6ad91831ef76b5c02619f554820 Mon Sep 17 00:00:00 2001 From: dudxo Date: Fri, 27 Sep 2024 20:01:04 +0900 Subject: [PATCH 12/12] =?UTF-8?q?[rename]=20:=20=EA=B8=B0=EC=A1=B4=20OAuth?= =?UTF-8?q?AccessToken=20=EC=A1=B4=EC=9E=AC=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=ED=9B=84=20=EC=82=AD=EC=A0=9C=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dnd/gongmuin/member/service/MemberService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/dnd/gongmuin/member/service/MemberService.java b/src/main/java/com/dnd/gongmuin/member/service/MemberService.java index ab3afe3f..dfa39adb 100644 --- a/src/main/java/com/dnd/gongmuin/member/service/MemberService.java +++ b/src/main/java/com/dnd/gongmuin/member/service/MemberService.java @@ -42,7 +42,7 @@ public Member saveOrUpdate(Oauth2Response oauth2Response) { Member member = memberRepository.findBySocialEmail(oauth2Response.createSocialEmail()) .map(m -> { m.updateSocialEmail(oauth2Response.createSocialEmail()); - deleteExistingOauthAccessToken(m); + deleteOauthAccessTokenIfExists(m); saveOauth2AccessToken(oauth2Response, m); return m; }) @@ -59,7 +59,7 @@ private void saveOauth2AccessToken(Oauth2Response oauth2Response, Member m) { ); } - private void deleteExistingOauthAccessToken(Member m) { + private void deleteOauthAccessTokenIfExists(Member m) { if (redisUtil.getValues("AT(oauth2):" + m.getSocialEmail()) != null) { redisUtil.deleteValues("AT(oauth2):" + m.getSocialEmail()); }