From 06972f4d6da7acd410497bc2b690cd238a972986 Mon Sep 17 00:00:00 2001 From: Kosw6 Date: Sat, 13 Dec 2025 00:36:07 +0900 Subject: [PATCH] [BE] edit CustomOAuth2UserService, UserOauthAccountRepository,TokenEncryptor --- .../auth/dao/UserOauthAccountRepository.java | 2 + .../auth/config/CustomOAuth2UserService.java | 107 +++++++++++------- .../common/auth/jwt/TokenEncryptor.java | 2 +- 3 files changed, 70 insertions(+), 41 deletions(-) diff --git a/backend/src/main/java/org/sejongisc/backend/auth/dao/UserOauthAccountRepository.java b/backend/src/main/java/org/sejongisc/backend/auth/dao/UserOauthAccountRepository.java index 8489a634..21d92029 100644 --- a/backend/src/main/java/org/sejongisc/backend/auth/dao/UserOauthAccountRepository.java +++ b/backend/src/main/java/org/sejongisc/backend/auth/dao/UserOauthAccountRepository.java @@ -2,6 +2,7 @@ import org.sejongisc.backend.auth.entity.AuthProvider; import org.sejongisc.backend.auth.entity.UserOauthAccount; +import org.sejongisc.backend.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -9,4 +10,5 @@ public interface UserOauthAccountRepository extends JpaRepository { Optional findByProviderAndProviderUid(AuthProvider provider, String providerUid); + boolean existsByProviderAndUser(AuthProvider provider, User user); } diff --git a/backend/src/main/java/org/sejongisc/backend/common/auth/config/CustomOAuth2UserService.java b/backend/src/main/java/org/sejongisc/backend/common/auth/config/CustomOAuth2UserService.java index 3ad7350c..78edab7e 100644 --- a/backend/src/main/java/org/sejongisc/backend/common/auth/config/CustomOAuth2UserService.java +++ b/backend/src/main/java/org/sejongisc/backend/common/auth/config/CustomOAuth2UserService.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; @Slf4j @Service @@ -32,10 +33,8 @@ public class CustomOAuth2UserService implements OAuth2UserService delegate = - new DefaultOAuth2UserService(); + + OAuth2UserService delegate = new DefaultOAuth2UserService(); OAuth2User oAuth2User = delegate.loadUser(req); String provider = req.getClientRegistration().getRegistrationId(); // google, kakao, github @@ -45,7 +44,6 @@ public OAuth2User loadUser(OAuth2UserRequest req) throws OAuth2AuthenticationExc String email; String name; - // log.info("[OAuth2] Provider = {}", provider); if (log.isDebugEnabled()) { log.debug("[OAuth2] Attributes = {}", attrs); } @@ -59,60 +57,89 @@ public OAuth2User loadUser(OAuth2UserRequest req) throws OAuth2AuthenticationExc case "kakao" -> { providerUid = attrs.get("id").toString(); Map kakaoAccount = (Map) attrs.get("kakao_account"); - email = (String) kakaoAccount.get("email"); // null 가능 - Map profile = (Map) kakaoAccount.get("profile"); - name = (String) profile.get("nickname"); + email = kakaoAccount == null ? null : (String) kakaoAccount.get("email"); // null 가능 + Map profile = kakaoAccount == null ? null : (Map) kakaoAccount.get("profile"); + name = profile == null ? null : (String) profile.get("nickname"); } case "github" -> { providerUid = attrs.get("id").toString(); - email = (String) attrs.get("email"); - name = (String) attrs.get("login"); // GitHub은 login이 닉네임 + email = (String) attrs.get("email"); // GitHub은 null 자주 나옴(권한/공개설정) + name = (String) attrs.get("login"); } default -> throw new RuntimeException("지원하지 않는 provider: " + provider); } - // log.info("provider={}, providerUid={}, email={}, name={}", provider, providerUid, email, name); - - final String fProviderUid = providerUid; - final String fEmail = email; - final String fName = name; - final AuthProvider fAuthProvider = AuthProvider.valueOf(provider.toUpperCase()); - - User user = oauthAccountRepository - .findByProviderAndProviderUid(AuthProvider.from(provider), providerUid) - .map(UserOauthAccount::getUser) - .orElseGet(() -> { - User newUser = User.builder() - .email(email) - .name(name) - .role(Role.TEAM_MEMBER) - .build(); - User saved = userRepository.save(newUser); + AuthProvider authProvider = AuthProvider.from(provider); + + // 1) (provider, providerUid)로 먼저 찾는다 (기존 OAuth 로그인 사용자) + Optional oauthLinkOpt = + oauthAccountRepository.findByProviderAndProviderUid(authProvider, providerUid); + + User user; + + if (oauthLinkOpt.isPresent()) { + user = oauthLinkOpt.get().getUser(); + } else { + // 2) 링크가 없으면 email로 기존 로컬/회원가입 유저를 찾는다 (핵심!) + Optional userByEmailOpt = Optional.empty(); + if (email != null && !email.isBlank()) { + userByEmailOpt = userRepository.findUserByEmail(email); + } + + if (userByEmailOpt.isPresent()) { + // 3) 기존 유저가 있으면: 새 유저 만들지 말고 OAuth 링크만 추가 + user = userByEmailOpt.get(); + // (선택) 이미 같은 provider로 링크가 있으면 중복 저장 방지 + // repository에 메서드 없으면 try/catch로 unique constraint에 맡겨도 됨 + boolean alreadyLinked = oauthAccountRepository + .existsByProviderAndUser(authProvider, user); + + if (!alreadyLinked) { UserOauthAccount oauth = UserOauthAccount.builder() - .user(saved) - .provider(AuthProvider.from(provider)) + .user(user) + .provider(authProvider) .providerUid(providerUid) .build(); oauthAccountRepository.save(oauth); - - return saved; - }); - - // log.info("[CustomOAuth2UserService] User resolved → returning OAuth2User"); + } + + } else { + // 4) email로도 못 찾으면 신규 생성 + 링크 + User newUser = User.builder() + .email(email) // null 가능성: DB 제약(NOT NULL/UNIQUE) 있으면 정책 필요 + .name(name) + .role(Role.TEAM_MEMBER) + .build(); + + User saved = userRepository.save(newUser); + + UserOauthAccount oauth = UserOauthAccount.builder() + .user(saved) + .provider(authProvider) + .providerUid(providerUid) + .build(); + oauthAccountRepository.save(oauth); + + user = saved; + } + } Map attributes = new java.util.HashMap<>(); - attributes.put("provider", provider); // google / kakao / github - attributes.put("providerUid", providerUid); // 소셜 계정 UID - attributes.put("email", user.getEmail()); // DB email + attributes.put("provider", provider); + attributes.put("providerUid", providerUid); + attributes.put("email", user.getEmail()); attributes.put("name", user.getName()); - attributes.put("userId", user.getUserId()); // DB user uuid + attributes.put("userId", user.getUserId()); return new DefaultOAuth2User( + // 기존처럼 고정 ROLE 주고 싶으면 그대로 두고, + // 권한을 user role 기반으로 주고 싶으면 아래 주석 라인으로 교체해도 됨 List.of(new SimpleGrantedAuthority("ROLE_TEAM_MEMBER")), + // List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())), attributes, - "userId" // 또는 "email" -> email null 이면 id가 더 안전 + "userId" ); - } + } diff --git a/backend/src/main/java/org/sejongisc/backend/common/auth/jwt/TokenEncryptor.java b/backend/src/main/java/org/sejongisc/backend/common/auth/jwt/TokenEncryptor.java index 73e60f0c..50586d9b 100644 --- a/backend/src/main/java/org/sejongisc/backend/common/auth/jwt/TokenEncryptor.java +++ b/backend/src/main/java/org/sejongisc/backend/common/auth/jwt/TokenEncryptor.java @@ -21,7 +21,7 @@ public class TokenEncryptor { private final SecretKeySpec secretKey; - public TokenEncryptor(@Value("${TOKEN_ENCRYPTION_KEY:mySecretKey1234}") String key) { + public TokenEncryptor(@Value("${TOKEN_ENCRYPTION_KEY:mySecretKey12345}") String key) { if (key == null || key.length() != 16) { throw new IllegalStateException( "유효한 16바이트 토큰 암호화 키가 설정되지 않았습니다. 환경변수 TOKEN_ENCRYPTION_KEY를 확인하세요.");