Skip to content

Commit 4e4fa23

Browse files
authored
Merge pull request #266 from DevKor-github/codex/handle-duplicate-google-users
[codex] Handle duplicate Google social users
2 parents ccdc434 + 89032ce commit 4e4fa23

5 files changed

Lines changed: 78 additions & 14 deletions

File tree

ontime-back/src/main/java/devkor/ontime_back/controller/SocialAuthController.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import devkor.ontime_back.dto.OAuthKakaoUserDto;
77
import devkor.ontime_back.global.oauth.apple.AppleLoginService;
88
import devkor.ontime_back.global.oauth.google.GoogleLoginService;
9+
import devkor.ontime_back.response.ApiResponseForm;
910
import devkor.ontime_back.service.UserAuthService;
1011
import io.swagger.v3.oas.annotations.Operation;
1112
import io.swagger.v3.oas.annotations.media.Content;
@@ -16,6 +17,7 @@
1617
import jakarta.servlet.http.HttpServletResponse;
1718
import lombok.RequiredArgsConstructor;
1819
import lombok.extern.slf4j.Slf4j;
20+
import org.springframework.http.ResponseEntity;
1921
import org.springframework.web.bind.annotation.*;
2022

2123
@Slf4j
@@ -113,24 +115,32 @@ public String appleRegisterOrLogin(@RequestBody OAuthAppleRequestDto appleLoginR
113115
summary = "애플 소셜 로그인 회원탈퇴"
114116
)
115117
@DeleteMapping("/apple/me")
116-
public String appleDeleteUser(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) FeedbackAddDto feedbackAddDto) throws Exception {
118+
public ResponseEntity<ApiResponseForm<?>> appleDeleteUser(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) FeedbackAddDto feedbackAddDto) {
117119
Long userId = userAuthService.getUserIdFromToken(request);
118120
log.info("userId: {}", userId);
119-
appleLoginService.revokeToken(userId);
121+
try {
122+
appleLoginService.revokeToken(userId);
123+
} catch (Exception e) {
124+
log.warn("Apple 토큰 철회에 실패했지만 계정 삭제를 계속 진행합니다. userId={}, reason={}", userId, e.getMessage());
125+
}
120126
userAuthService.deleteUser(userId, feedbackAddDto);
121-
return "애플 로그인 회원탈퇴 성공";
127+
return ResponseEntity.ok(ApiResponseForm.success(null, "애플 로그인 회원탈퇴 성공"));
122128
}
123129

124130
@Operation(
125131
summary = "구글 소셜 로그인 회원탈퇴"
126132
)
127133
@DeleteMapping("/google/me")
128-
public String googleDeleteUser(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) FeedbackAddDto feedbackAddDto) throws Exception {
134+
public ResponseEntity<ApiResponseForm<?>> googleDeleteUser(HttpServletRequest request, HttpServletResponse response, @RequestBody(required = false) FeedbackAddDto feedbackAddDto) {
129135
Long userId = userAuthService.getUserIdFromToken(request);
130136
log.info("userId: {}", userId);
131-
googleLoginService.revokeToken(userId);
137+
try {
138+
googleLoginService.revokeToken(userId);
139+
} catch (Exception e) {
140+
log.warn("Google 토큰 철회에 실패했지만 계정 삭제를 계속 진행합니다. userId={}, reason={}", userId, e.getMessage());
141+
}
132142
userAuthService.deleteUser(userId, feedbackAddDto);
133-
return "구글 로그인 회원탈퇴 성공";
143+
return ResponseEntity.ok(ApiResponseForm.success(null, "구글 로그인 회원탈퇴 성공"));
134144
}
135145

136146

ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginFilter.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212
import jakarta.servlet.http.HttpServletRequest;
1313
import jakarta.servlet.http.HttpServletResponse;
1414
import lombok.extern.slf4j.Slf4j;
15+
import org.springframework.dao.DataIntegrityViolationException;
1516
import org.springframework.security.core.Authentication;
1617
import org.springframework.security.core.AuthenticationException;
1718
import org.springframework.security.core.context.SecurityContextHolder;
1819
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
1920

2021
import java.io.IOException;
22+
import java.util.List;
2123
import java.util.Map;
22-
import java.util.Optional;
2324
import java.util.concurrent.ConcurrentHashMap;
2425

2526
@Slf4j
@@ -48,13 +49,26 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
4849

4950
Object loginLock = LOGIN_LOCKS.computeIfAbsent(googleUserId, key -> new Object());
5051
synchronized (loginLock) {
51-
Optional<User> existingUser = userRepository.findBySocialTypeAndSocialId(SocialType.GOOGLE, googleUserId);
52-
53-
if (existingUser.isPresent()) {
54-
return googleLoginService.handleLogin(oAuthGoogleRequestDto, existingUser.get(), response);
52+
List<User> existingUsers = userRepository.findAllBySocialTypeAndSocialIdOrderByIdDesc(SocialType.GOOGLE, googleUserId);
53+
54+
if (!existingUsers.isEmpty()) {
55+
if (existingUsers.size() > 1) {
56+
log.warn("동일한 Google socialId를 가진 유저가 {}명 존재합니다. 최신 userId={} 계정으로 로그인합니다.",
57+
existingUsers.size(), existingUsers.get(0).getId());
58+
}
59+
return googleLoginService.handleLogin(oAuthGoogleRequestDto, existingUsers.get(0), response);
5560
} else {
5661
OAuthGoogleUserDto oAuthGoogleUserDto = new OAuthGoogleUserDto(googleUserId, (String) googlePayload.get("name"), (String) googlePayload.get("picture"), googlePayload.getEmail());
57-
return googleLoginService.handleRegister(oAuthGoogleRequestDto, oAuthGoogleUserDto, response);
62+
try {
63+
return googleLoginService.handleRegister(oAuthGoogleRequestDto, oAuthGoogleUserDto, response);
64+
} catch (DataIntegrityViolationException e) {
65+
log.warn("Google 회원가입 중 중복 socialId가 감지되어 기존 계정으로 로그인합니다. socialId={}", googleUserId);
66+
User user = userRepository.findAllBySocialTypeAndSocialIdOrderByIdDesc(SocialType.GOOGLE, googleUserId)
67+
.stream()
68+
.findFirst()
69+
.orElseThrow(() -> e);
70+
return googleLoginService.handleLogin(oAuthGoogleRequestDto, user, response);
71+
}
5872
}
5973
}
6074

ontime-back/src/main/java/devkor/ontime_back/global/oauth/google/GoogleLoginService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.http.HttpHeaders;
2323
import org.springframework.http.HttpMethod;
2424
import org.springframework.http.ResponseEntity;
25+
import org.springframework.http.client.SimpleClientHttpRequestFactory;
2526
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
2627
import org.springframework.security.core.Authentication;
2728
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -175,7 +176,10 @@ public boolean revokeToken(Long userId) {
175176

176177
String googleRefreshToken = user.getSocialLoginToken();
177178

178-
RestTemplate restTemplate = new RestTemplate();
179+
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
180+
requestFactory.setConnectTimeout(3000);
181+
requestFactory.setReadTimeout(3000);
182+
RestTemplate restTemplate = new RestTemplate(requestFactory);
179183
String revokeUrl = GOOGLE_REVOKE_URL + googleRefreshToken;
180184

181185
HttpHeaders headers = new HttpHeaders();

ontime-back/src/main/java/devkor/ontime_back/repository/UserRepository.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.springframework.data.jpa.repository.Query;
77
import org.springframework.stereotype.Repository;
88

9+
import java.util.List;
910
import java.util.Optional;
1011

1112
@Repository
@@ -21,8 +22,10 @@ public interface UserRepository extends JpaRepository<User, Long> {
2122
// 추가정보 입력받을때 사용
2223
Optional<User> findBySocialTypeAndSocialId(SocialType socialType, String socialId);
2324

25+
List<User> findAllBySocialTypeAndSocialIdOrderByIdDesc(SocialType socialType, String socialId);
26+
2427
@Query("SELECT u.spareTime FROM User u WHERE u.id = :id")
2528
Integer findSpareTimeById(Long id);
2629

2730
Optional<Object> findByAccessToken(String token);
28-
}
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
DELETE u
2+
FROM user u
3+
JOIN (
4+
SELECT * FROM (
5+
SELECT social_type, social_id, MAX(user_id) AS keep_user_id
6+
FROM user
7+
WHERE social_type IS NOT NULL
8+
AND social_id IS NOT NULL
9+
GROUP BY social_type, social_id
10+
HAVING COUNT(*) > 1
11+
) duplicate_groups
12+
) d
13+
ON u.social_type = d.social_type
14+
AND u.social_id = d.social_id
15+
WHERE u.user_id <> d.keep_user_id;
16+
17+
SET @constraint_exists = (
18+
SELECT COUNT(*)
19+
FROM information_schema.table_constraints
20+
WHERE constraint_schema = DATABASE()
21+
AND table_name = 'user'
22+
AND constraint_name = 'uk_user_social_type_social_id'
23+
);
24+
25+
SET @add_constraint_sql = IF(
26+
@constraint_exists = 0,
27+
'ALTER TABLE user ADD CONSTRAINT uk_user_social_type_social_id UNIQUE (social_type, social_id)',
28+
'SELECT 1'
29+
);
30+
31+
PREPARE add_constraint_statement FROM @add_constraint_sql;
32+
EXECUTE add_constraint_statement;
33+
DEALLOCATE PREPARE add_constraint_statement;

0 commit comments

Comments
 (0)