99import org .sejongisc .backend .user .dao .UserRepository ;
1010import org .sejongisc .backend .user .entity .Role ;
1111import org .sejongisc .backend .user .entity .User ;
12- import org .sejongisc .backend .user .service .UserServiceImpl ;
1312import org .springframework .security .core .authority .SimpleGrantedAuthority ;
1413import org .springframework .security .oauth2 .client .userinfo .DefaultOAuth2UserService ;
1514import org .springframework .security .oauth2 .client .userinfo .OAuth2UserRequest ;
2726@ Service
2827@ RequiredArgsConstructor
2928@ Transactional
30- public class CustomOAuth2UserService implements OAuth2UserService <OAuth2UserRequest , OAuth2User > {
29+ public class CustomOAuth2UserService implements OAuth2UserService <OAuth2UserRequest , OAuth2User > {
30+
3131 private final UserRepository userRepository ;
3232 private final UserOauthAccountRepository oauthAccountRepository ;
3333
@@ -71,6 +71,11 @@ public OAuth2User loadUser(OAuth2UserRequest req) throws OAuth2AuthenticationExc
7171
7272 AuthProvider authProvider = AuthProvider .from (provider );
7373
74+ // ✅ 선택지 A: email이 없으면 가상 이메일 생성 (User 생성용)
75+ if (email == null || email .isBlank ()) {
76+ email = provider + "_" + providerUid + "@oauth.local" ;
77+ }
78+
7479 // 1) (provider, providerUid)로 먼저 찾는다 (기존 OAuth 로그인 사용자)
7580 Optional <UserOauthAccount > oauthLinkOpt =
7681 oauthAccountRepository .findByProviderAndProviderUid (authProvider , providerUid );
@@ -82,19 +87,15 @@ public OAuth2User loadUser(OAuth2UserRequest req) throws OAuth2AuthenticationExc
8287 } else {
8388 // 2) 링크가 없으면 email로 기존 로컬/회원가입 유저를 찾는다 (핵심!)
8489 Optional <User > userByEmailOpt = Optional .empty ();
85- if (email != null && !email .isBlank ()) {
90+ if (!email .isBlank ()) {
8691 userByEmailOpt = userRepository .findUserByEmail (email );
8792 }
8893
8994 if (userByEmailOpt .isPresent ()) {
9095 // 3) 기존 유저가 있으면: 새 유저 만들지 말고 OAuth 링크만 추가
9196 user = userByEmailOpt .get ();
9297
93- // (선택) 이미 같은 provider로 링크가 있으면 중복 저장 방지
94- // repository에 메서드 없으면 try/catch로 unique constraint에 맡겨도 됨
95- boolean alreadyLinked = oauthAccountRepository
96- .existsByProviderAndUser (authProvider , user );
97-
98+ boolean alreadyLinked = oauthAccountRepository .existsByProviderAndUser (authProvider , user );
9899 if (!alreadyLinked ) {
99100 UserOauthAccount oauth = UserOauthAccount .builder ()
100101 .user (user )
@@ -105,23 +106,40 @@ public OAuth2User loadUser(OAuth2UserRequest req) throws OAuth2AuthenticationExc
105106 }
106107
107108 } else {
108- // 4) email로도 못 찾으면 신규 생성 + 링크
109- User newUser = User .builder ()
110- .email (email ) // null 가능성: DB 제약(NOT NULL/UNIQUE) 있으면 정책 필요
111- .name (name )
112- .role (Role .TEAM_MEMBER )
113- .build ();
114-
115- User saved = userRepository .save (newUser );
116-
117- UserOauthAccount oauth = UserOauthAccount .builder ()
118- .user (saved )
119- .provider (authProvider )
120- .providerUid (providerUid )
121- .build ();
122- oauthAccountRepository .save (oauth );
123-
124- user = saved ;
109+ // 4) email로도 못 찾으면 신규 생성 + 링크 (경합 대비 try/catch)
110+ try {
111+ User newUser = User .builder ()
112+ .email (email )
113+ .name (name )
114+ .role (Role .TEAM_MEMBER )
115+ .build ();
116+
117+ User saved = userRepository .save (newUser );
118+
119+ UserOauthAccount oauth = UserOauthAccount .builder ()
120+ .user (saved )
121+ .provider (authProvider )
122+ .providerUid (providerUid )
123+ .build ();
124+ oauthAccountRepository .save (oauth );
125+
126+ user = saved ;
127+
128+ } catch (org .springframework .dao .DataIntegrityViolationException e ) {
129+ // 동시에 다른 트랜잭션이 같은 이메일 유저를 먼저 만들어버린 경우(UK 충돌)
130+ User existing = userRepository .findUserByEmail (email )
131+ .orElseThrow (() -> e );
132+
133+ if (!oauthAccountRepository .existsByProviderAndUser (authProvider , existing )) {
134+ UserOauthAccount oauth = UserOauthAccount .builder ()
135+ .user (existing )
136+ .provider (authProvider )
137+ .providerUid (providerUid )
138+ .build ();
139+ oauthAccountRepository .save (oauth );
140+ }
141+ user = existing ;
142+ }
125143 }
126144 }
127145
@@ -133,13 +151,9 @@ public OAuth2User loadUser(OAuth2UserRequest req) throws OAuth2AuthenticationExc
133151 attributes .put ("userId" , user .getUserId ());
134152
135153 return new DefaultOAuth2User (
136- // 기존처럼 고정 ROLE 주고 싶으면 그대로 두고,
137- // 권한을 user role 기반으로 주고 싶으면 아래 주석 라인으로 교체해도 됨
138154 List .of (new SimpleGrantedAuthority ("ROLE_TEAM_MEMBER" )),
139- // List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())),
140155 attributes ,
141156 "userId"
142157 );
143158 }
144-
145159}
0 commit comments