Skip to content

Commit 9b47603

Browse files
authored
🔀 Merge pull request #110 from Boggle-Boggle/feature/#100
✨ 회원탈퇴시 인증서버 /revoke 추가
2 parents 16da495 + 4e8f9af commit 9b47603

File tree

9 files changed

+205
-11
lines changed

9 files changed

+205
-11
lines changed

src/main/java/com/boggle_boggle/bbegok/config/properties/AppleProperties.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,24 @@
2525
import java.util.Map;
2626

2727
@Setter
28+
@Getter
2829
@Component
2930
@ConfigurationProperties(prefix = "apple")
3031
public class AppleProperties {
31-
3232
private String teamId;
3333
private String keyId;
3434
private String keyPath;
3535
private String redirectUri;
3636
private String iss;
3737
private String aud; //client-id
38+
private Auth auth;
39+
40+
@Setter
41+
public static class Auth {
42+
private String tokenUrl; //토큰을 획득하기 위해 앱에 전달된 권한 부여 코드 확인
43+
private String publicKeyUrl;
44+
private String revokeUrl;
45+
}
3846

3947
public String getAppleLoginUrl(String redirectUri) {
4048
return iss + "/auth/authorize"
@@ -44,6 +52,14 @@ public String getAppleLoginUrl(String redirectUri) {
4452
+ "&state=" + redirectUri;
4553
}
4654

55+
56+
public String getAppleRevokeData(String accessToken) throws IOException {
57+
return "client_id=" + aud
58+
+"&client_secret=" + createClientSecretKey()
59+
+"&token=" + accessToken
60+
+"&token_type_hint=access_token";
61+
}
62+
4763
public String generateAuthToken(String code) throws IOException {
4864
if (code == null) throw new IllegalArgumentException("Failed get authorization code");
4965

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.boggle_boggle.bbegok.controller;
2+
3+
import com.boggle_boggle.bbegok.dto.base.DataResponseDto;
4+
import com.boggle_boggle.bbegok.service.RevokeService;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
7+
import org.springframework.security.core.userdetails.UserDetails;
8+
import org.springframework.web.bind.annotation.DeleteMapping;
9+
import org.springframework.web.bind.annotation.RequestHeader;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
import java.io.IOException;
13+
14+
@RestController
15+
@RequiredArgsConstructor
16+
public class RevokeController {
17+
private final RevokeService revokeService;
18+
19+
@DeleteMapping("/oauth2/revoke")
20+
public DataResponseDto<Void> revokeAccount(@AuthenticationPrincipal UserDetails userDetails) throws IOException {
21+
revokeService.deleteAccount(userDetails.getUsername());
22+
return DataResponseDto.empty();
23+
}
24+
}

src/main/java/com/boggle_boggle/bbegok/controller/UserController.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.boggle_boggle.bbegok.dto.base.DataResponseDto;
55
import com.boggle_boggle.bbegok.dto.request.NickNameRequest;
66
import com.boggle_boggle.bbegok.dto.response.TermsResponse;
7+
import com.boggle_boggle.bbegok.service.RevokeService;
78
import com.boggle_boggle.bbegok.service.UserService;
89
import com.boggle_boggle.bbegok.utils.CookieUtil;
910
import jakarta.servlet.http.HttpServletRequest;
@@ -17,21 +18,23 @@
1718
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.DEVICE_CODE;
1819
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.REFRESH_TOKEN;
1920

21+
import java.io.IOException;
2022
import java.util.List;
2123

2224
@RestController
2325
@RequiredArgsConstructor
2426
@RequestMapping("/user")
2527
public class UserController {
2628

29+
private final RevokeService revokeService;
2730
private final UserService userService;
2831

2932
//회원탈퇴
33+
// @DeleteMapping("/oauth2/revoke")
3034
@DeleteMapping
31-
public DataResponseDto<Null> deleteUser(HttpServletRequest request, HttpServletResponse response, @AuthenticationPrincipal UserDetails userDetails) {
32-
CookieUtil.deleteCookie(request, response, REFRESH_TOKEN);
33-
CookieUtil.deleteCookie(request, response, DEVICE_CODE);
34-
userService.deleteUser(userDetails.getUsername());
35+
public DataResponseDto<Void> deleteUser(HttpServletRequest request, HttpServletResponse response, @AuthenticationPrincipal UserDetails userDetails) throws IOException {
36+
revokeService.deleteAccount(userDetails.getUsername());
37+
//userService.deleteUser(request, response, userDetails.getUsername());
3538
return DataResponseDto.empty();
3639
}
3740

src/main/java/com/boggle_boggle/bbegok/entity/Library.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class Library {
1919
@JoinColumn(name = "user_seq")
2020
private User user;
2121

22-
@Column(name = "library_name", nullable = false)
22+
@Column(name = "library_name", nullable = false, length = 15)
2323
private String libraryName;
2424

2525
@OneToMany(mappedBy = "library", cascade = CascadeType.ALL, orphanRemoval = true)

src/main/java/com/boggle_boggle/bbegok/entity/Terms.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class Terms {
1919
@Column(name = "title", nullable = false)
2020
private String title;
2121

22-
@Column(name = "content", nullable = false)
22+
@Column(name = "content", nullable = false, length = 1000)
2323
private String content;
2424

2525
@Column(name = "version", nullable = false)

src/main/java/com/boggle_boggle/bbegok/entity/user/User.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class User {
2929
@Size(max = 64)
3030
private String userId;
3131

32-
@Column(name = "user_name", unique = true, length = 20)
32+
@Column(name = "user_name", unique = true, length = 12)
3333
private String userName = null;
3434

3535
@JsonIgnore
@@ -39,7 +39,7 @@ public class User {
3939

4040
@JsonIgnore
4141
@Column(name = "access_token", length = 512)
42-
private String accessToken;
42+
private String oauth2AccessToken;
4343

4444
@Column(name = "email", length = 512, unique = true, nullable = true)
4545
@Size(max = 512)
@@ -98,6 +98,24 @@ protected User(
9898
}
9999

100100

101+
protected User(
102+
String userId,
103+
String emailVerifiedYn,
104+
ProviderType providerType,
105+
RoleType roleType,
106+
LocalDateTime createdAt,
107+
LocalDateTime modifiedAt,
108+
String accessToken) {
109+
this.userId = userId;
110+
this.emailVerifiedYn = emailVerifiedYn;
111+
this.providerType = providerType;
112+
this.roleType = roleType;
113+
this.createdAt = createdAt;
114+
this.modifiedAt = modifiedAt;
115+
this.oauth2AccessToken = accessToken;
116+
}
117+
118+
101119
public static User createUser(
102120
@NotNull String userId,
103121
@NotNull String emailVerifiedYn,
@@ -117,7 +135,7 @@ public static User createUser(
117135
@NotNull LocalDateTime modifiedAt,
118136
@NotNull String accessToken
119137
){
120-
return new User(userId, emailVerifiedYn, providerType, roleType, createdAt, modifiedAt);
138+
return new User(userId, emailVerifiedYn, providerType, roleType, createdAt, modifiedAt, accessToken);
121139
}
122140

123141
public void updateNickName(String nickName){
@@ -134,4 +152,8 @@ public void updateGuestToUser(String latestVersion) {
134152
this.roleType = RoleType.USER;
135153
this.agreedVersion = latestVersion;
136154
}
155+
156+
public void updateAccessToken(String accessToken) {
157+
this.oauth2AccessToken = accessToken;
158+
}
137159
}

src/main/java/com/boggle_boggle/bbegok/service/AppleService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public User process(String code) {
7878
" account. Please use your " + savedUser.getProviderType() + " account to login."
7979
);
8080
}
81+
savedUser.updateAccessToken(accessToken);
8182
} else {
8283
savedUser = createAppleUser(userId,accessToken);
8384
userSettingsRepository.saveAndFlush(UserSettings.createUserSettings(savedUser));
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.boggle_boggle.bbegok.service;
2+
3+
import com.boggle_boggle.bbegok.config.properties.AppleProperties;
4+
import com.boggle_boggle.bbegok.entity.user.User;
5+
import com.boggle_boggle.bbegok.exception.Code;
6+
import com.boggle_boggle.bbegok.exception.exception.GeneralException;
7+
import com.boggle_boggle.bbegok.oauth.entity.ProviderType;
8+
import com.boggle_boggle.bbegok.repository.user.UserRefreshTokenRepository;
9+
import com.boggle_boggle.bbegok.repository.user.UserRepository;
10+
import com.boggle_boggle.bbegok.utils.CookieUtil;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.beans.factory.annotation.Value;
14+
import org.springframework.http.*;
15+
import org.springframework.stereotype.Service;
16+
import org.springframework.web.client.RestTemplate;
17+
18+
import java.io.IOException;
19+
import java.security.AuthProvider;
20+
import java.util.Optional;
21+
22+
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.DEVICE_CODE;
23+
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.REFRESH_TOKEN;
24+
25+
@Slf4j
26+
@RequiredArgsConstructor
27+
@Service
28+
public class RevokeService {
29+
private final UserRepository userRepository;
30+
private final AppleProperties appleProperties;
31+
private final UserRefreshTokenRepository userRefreshTokenRepository;
32+
private final AppleService appleService;
33+
34+
@Value("${apple.revoke-url}")
35+
String appleRevokeUrl;
36+
@Value("${google.revoke-url}")
37+
String googleRevokeUrl;
38+
@Value("${kakao.revoke-url}")
39+
String kakaoRevokeUrl;
40+
41+
//(1) User 삭제
42+
//(2) 인증서버의 액세스토큰 무효화
43+
//(3) 쿠키제거
44+
45+
public void deleteAccount(String userId) throws IOException {
46+
User user = getUser(userId);
47+
deleteUser(user);
48+
49+
switch (user.getProviderType()) {
50+
case APPLE -> deleteAppleAccount(user);
51+
case GOOGLE -> deleteGoogleAccount(user);
52+
case KAKAO -> deleteKakaoAccount(user);
53+
}
54+
}
55+
56+
public void deleteAppleAccount(User user) throws IOException {
57+
String data = appleProperties.getAppleRevokeData(user.getOauth2AccessToken());
58+
sendRevokeRequest(data, ProviderType.APPLE, null);
59+
}
60+
61+
public void deleteGoogleAccount(User user) {
62+
String data = "token=" + user.getOauth2AccessToken();
63+
sendRevokeRequest(data, ProviderType.GOOGLE, null);
64+
}
65+
66+
public void deleteKakaoAccount(User user) {
67+
sendRevokeRequest(null, ProviderType.KAKAO, user.getOauth2AccessToken());
68+
}
69+
70+
71+
private void deleteUser(User user) {
72+
user.softDelete();
73+
userRefreshTokenRepository.deleteByUser(user);
74+
}
75+
76+
/**
77+
* @param data : revoke request의 body에 들어갈 데이터
78+
* @param provider : oauth2 업체
79+
* @param accessToken : 카카오의 경우 url이 아니라 헤더에 access token을 첨부해서 보내줘야 함
80+
*/
81+
private void sendRevokeRequest(String data, ProviderType provider, String accessToken) {
82+
RestTemplate restTemplate = new RestTemplate();
83+
String revokeUrl = "";
84+
85+
HttpHeaders headers = new HttpHeaders();
86+
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
87+
88+
HttpEntity<String> entity = new HttpEntity<>(data, headers);
89+
90+
switch (provider) {
91+
case APPLE -> revokeUrl = appleRevokeUrl;
92+
case GOOGLE -> revokeUrl = googleRevokeUrl;
93+
case KAKAO -> {
94+
revokeUrl = kakaoRevokeUrl;
95+
headers.setBearerAuth(accessToken);
96+
}
97+
}
98+
99+
ResponseEntity<String> responseEntity = restTemplate.exchange(revokeUrl, HttpMethod.POST, entity, String.class);
100+
101+
HttpStatus statusCode = (HttpStatus) responseEntity.getStatusCode();
102+
String responseBody = responseEntity.getBody();
103+
104+
// 제공업체 정보를 포함한 간단한 로그 기록
105+
log.debug("[{}] 소셜 회원 연결해제 요청 결과", provider.name());
106+
log.debug("[{}] Status Code: {}", provider.name(), statusCode);
107+
log.debug("[{}] Response: {}", provider.name(), responseBody);
108+
}
109+
110+
public User getUser(String userSeq) {
111+
User user = userRepository.findByUserSeqAndIsDeleted(Long.valueOf(userSeq), false);
112+
if(user == null) {
113+
//탈퇴한 적 있는 회원
114+
if(userRepository.countByUserSeqAndIsDeleted(Long.valueOf(userSeq), true) > 0) throw new GeneralException(Code.USER_ALREADY_WITHDRAWN);
115+
else throw new GeneralException(Code.USER_NOT_FOUND);
116+
}
117+
return user;
118+
}
119+
}

src/main/java/com/boggle_boggle/bbegok/service/UserService.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@
1414
import com.boggle_boggle.bbegok.repository.redis.TermsRepository;
1515
import com.boggle_boggle.bbegok.repository.user.UserRefreshTokenRepository;
1616
import com.boggle_boggle.bbegok.repository.user.UserRepository;
17+
import com.boggle_boggle.bbegok.utils.CookieUtil;
18+
import jakarta.servlet.http.HttpServletRequest;
19+
import jakarta.servlet.http.HttpServletResponse;
1720
import lombok.RequiredArgsConstructor;
1821
import org.springframework.stereotype.Service;
1922
import org.springframework.transaction.annotation.Transactional;
2023

2124
import java.util.*;
2225

26+
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.DEVICE_CODE;
27+
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.REFRESH_TOKEN;
28+
2329
@Service
2430
@RequiredArgsConstructor
2531
@Transactional
@@ -41,7 +47,10 @@ public User getUser(String userSeq) {
4147
}
4248

4349
//Soft Delete를 위해 User컬럼 업데이트 및 토큰DB 삭제처리
44-
public void deleteUser(String userSeq) {
50+
public void deleteUser(HttpServletRequest request, HttpServletResponse response, String userSeq) {
51+
CookieUtil.deleteCookie(request, response, REFRESH_TOKEN);
52+
CookieUtil.deleteCookie(request, response, DEVICE_CODE);
53+
4554
User user = getUser(userSeq);
4655
user.softDelete();
4756
userRefreshTokenRepository.deleteByUser(user);

0 commit comments

Comments
 (0)