Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,25 @@ public static User emailUser(String email, String password, String username, Str
/*
OAuth2로 로그인한 유저 저장
*/
public static User googleUser(String email, String username){
public static User googleUser(String email, String username, String nickname){
return User.builder()
.email(email)
.username(username)
.authType(AuthType.GOOGLE)
.nickname("user_" + UUID.randomUUID().toString().split("-")[0])//닉네임은 자동으로 생성해주고, 나중에 수정할 수 있도록 함
.nickname(nickname)//닉네임은 자동으로 생성해주고, 나중에 수정할 수 있도록 함
.tier(Tier.NEWBIE)
.role(UserRole.USER)
.isDeleted(false)
.password(UUID.randomUUID().toString())
.build();
Comment on lines +94 to 104
Copy link

@coderabbitai coderabbitai bot Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

랜덤 비밀번호 평문 저장은 보안 위험

OAuth 가입 시 password(UUID.randomUUID().toString())로 무작위 문자열을 그대로 저장하고 있습니다.
비밀번호를 사용하지 않는 계정이라면 NULL 처리하거나, 최소한 PasswordEncoder로 해싱한 값을 저장하여 평문 노출을 방지해주세요.

- .password(UUID.randomUUID().toString())
+ // OAuth 계정은 비밀번호 미사용 → NULL 처리
+ .password(null)

또는,

- .password(UUID.randomUUID().toString())
+ .password(PasswordEncoderFactories.createDelegatingPasswordEncoder()
+            .encode(UUID.randomUUID().toString()))

Also applies to: 107-118

🤖 Prompt for AI Agents
In src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java around
lines 94 to 104 and 107 to 118, the code stores a random password as plain text
for OAuth users, which is a security risk. To fix this, either set the password
field to null if it is not used or encode the random password using
PasswordEncoder before saving it to avoid storing plain text passwords.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알아서할게 토끼야

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kimminu7 알겠습니다! 필요하시면 언제든지 말씀해 주세요.

}

public static User githubUser(String email, String username){
public static User githubUser(String email, String username,String nickname){
return User.builder()
.email(email)
.username(username)
.authType(AuthType.GITHUB)
.nickname("user_" + UUID.randomUUID().toString().split("-")[0])//닉네임은 자동으로 생성해주고, 나중에 수정할 수 있도록 함
.nickname(nickname)//닉네임은 자동으로 생성해주고, 나중에 수정할 수 있도록 함
.tier(Tier.NEWBIE)
.role(UserRole.USER)
.password(UUID.randomUUID().toString())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.ezcode.codetest.domain.user.model.enums;

public enum Adjective {
멋있는, 용감한, 지혜로운, 화려한, 귀여운, 빠른, 우아한, 차분한, 활발한, 낙천적인,
신중한, 다정한, 수줍은, 배려심깊은, 온화한, 고요한, 명랑한, 정직한, 활기찬, 친절한,
대담한, 유쾌한, 상냥한, 유능한, 청명한, 명석한, 순수한, 부지런한, 용의주도한, 근면한,
사려깊은, 명민한, 단호한, 냉철한, 호기심많은, 생기있는, 열정적인, 자유로운, 모험적인, 헌신적인,
깔끔한, 독창적인, 진중한, 똑똑한, 실용적인, 신비한, 젠틀한, 유연한, 다재다능한, 배짱있는,
순발력있는, 선량한, 인내심강한, 집중력있는, 민첩한, 풍부한, 과감한, 창의적인, 합리적인, 끈질긴,
호감가는, 매력적인, 성실한, 강직한, 강한, 따뜻한, 겸손한, 깨끗한, 솔직한, 여유로운,
정중한, 낭만적인, 조용한, 담백한, 균형잡힌, 용맹한, 재치있는, 평화로운, 반듯한, 침착한,
성격좋은, 책임감있는, 다감한, 푸근한, 온순한, 시원한, 명쾌한, 적응력강한, 근엄한, 정돈된,
유쾌발랄한, 기운찬, 생동감있는, 적극적인, 야무진, 통찰력있는, 포용력있는, 추진력있는, 자제력있는, 열의있는
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.ezcode.codetest.domain.user.model.enums;

public enum Noun {
다람쥐, 여우, 사자, 독수리, 고양이, 토끼, 늑대, 부엉이, 코끼리, 호랑이,
펭귄, 고래, 하마, 치타, 스컹크, 개미, 판다, 미어캣, 침팬지, 바다표범,
거북이, 흑표범, 제비, 두루미, 수달, 기린, 삵, 두더지, 캥거루, 백조,
청설모, 돌고래, 까마귀, 다람이, 삽살개, 들소, 갈매기, 두꺼비, 족제비, 코뿔소,
산양, 올빼미, 알파카, 스라소니, 수리부엉이, 원숭이, 문어, 뱀장어, 메추라기, 기러기,
앵무새, 너구리, 청둥오리, 고슴도치, 사슴, 아르마딜로, 말, 버팔로, 쥐, 뱀,
까치, 도마뱀, 너털곰, 스컹크곰, 오소리, 타조, 얼룩말, 해달, 족제비쥐, 앵무,
붉은여우, 산호, 코끼리개미, 사마귀, 박쥐, 해파리, 해마, 노루, 담비, 고라니,
공작, 새우, 매, 독수리부엉이, 개, 고양이상어, 악어, 큰눈도롱뇽, 망토원숭이, 비버,
코알라, 바다사자, 기린사슴, 비둘기, 토끼상어, 족제비상어, 검독수리, 황조롱이, 흰수염고래, 퓨마
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ public interface UserRepository {
Optional<User> findUserById(Long id);

User findByEmailAndProvider(String email, String provider);

boolean existsByNickname(String nickname);

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

private final UserRepository userRepository;
private final UserDomainService userDomainService;

@Override
@Transactional
Expand Down Expand Up @@ -51,7 +52,8 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
User findUser = userRepository.getUserByEmail(oAuth2Response.getEmail());

if (findUser == null) {
User newUser = User.googleUser(oAuth2Response.getEmail(), username);
String nickname = userDomainService.generateUniqueNickname();
User newUser = User.googleUser(oAuth2Response.getEmail(), username, nickname);
log.info("newUser: {} 새로운 유저", newUser);
Comment on lines +55 to 57
Copy link

@coderabbitai coderabbitai bot Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

닉네임 생성 실패 시 예외 유형 매핑 필요

userDomainService.generateUniqueNickname() 내부에서 IllegalStateException이 발생할 수 있는데, 현재 메서드에서는 그대로 전파되어 OAuth2AuthenticationException으로 래핑되지 않습니다.
OAuth 흐름에서 발생하는 모든 예외를 OAuth2AuthenticationException으로 맞춰두면 필터 단계에서 일관되게 처리할 수 있으므로, 아래와 같이 예외 매핑을 권장합니다.

try {
+    String nickname = userDomainService.generateUniqueNickname();
+    User newUser = User.googleUser(oAuth2Response.getEmail(), username, nickname);
+    userRepository.createUser(newUser);
-    String nickname = userDomainService.generateUniqueNickname();
-    User newUser = User.googleUser(oAuth2Response.getEmail(), username, nickname);
-    userRepository.createUser(newUser);
} catch (IllegalStateException e) {
    log.error("중복 닉네임 생성 실패 : {}", e.getMessage());
    throw new OAuth2AuthenticationException("닉네임 생성 실패입니다");
} catch (Exception e) {
    ...
}

generateUniqueNickname 메서드 정의와 CustomOAuth2UserService 내 호출 부분을 확인하기 위해 아래 스크립트를 실행하겠습니다.


🏁 Script executed:

#!/bin/bash
set -e

echo "=== generateUniqueNickname 정의 위치 및 예외 선언 확인 ==="
rg -n "generateUniqueNickname" -C3 src

echo ""
echo "=== CustomOAuth2UserService 내 호출 위치 및 예외 처리 현황 ==="
rg -n "generateUniqueNickname" -C10 src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java

Length of output: 2724


generateUniqueNickname 메서드의 구현 전체와 IllegalStateException 발생 지점을 확인하기 위해 아래 스크립트를 실행하겠습니다.


🏁 Script executed:

#!/bin/bash
set -e

echo "=== generateUniqueNickname 전체 구현 확인 ==="
rg -n "public String generateUniqueNickname" -C20 src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java

echo ""
echo "=== IllegalStateException throw 여부 확인 ==="
rg -n "throw new IllegalStateException" -C2 src/main/java/org/ezcode/codetest/domain/user/service/UserDomainService.java

Length of output: 1814


닉네임 생성 실패 예외 매핑 추가 필요

UserDomainService.generateUniqueNickname()는 1천만 번 시도 후에도 고유 닉네임을 찾지 못하면
IllegalStateException("중복된 닉네임 생성 불가")를 던집니다.
현재 CustomOAuth2UserService에서는 이 예외가 try 블록 바깥에 있어 필터 단계에서 일관된 OAuth2AuthenticationException 처리 경로를 타지 못합니다.
아래처럼 try 범위를 확장해 IllegalStateException을 별도로 잡아 OAuth2AuthenticationException으로 변환해 주세요.

  • 대상 파일:
    src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java (54~64행)
@@ if (findUser == null) {
-    String nickname = userDomainService.generateUniqueNickname();
-    User newUser = User.googleUser(oAuth2Response.getEmail(), username, nickname);
-    log.info("newUser: {} 새로운 유저", newUser);
-    try {
-        userRepository.createUser(newUser);
-    } catch (Exception e) {
-        log.error("OAuth 사용자 생성 실패 : {}", e.getMessage());
-        throw new OAuth2AuthenticationException("사용자 생성 실패입니다");
-    }
+    try {
+        String nickname = userDomainService.generateUniqueNickname();
+        User newUser = User.googleUser(oAuth2Response.getEmail(), username, nickname);
+        log.info("newUser: {} 새로운 유저", newUser);
+        userRepository.createUser(newUser);
+    } catch (IllegalStateException e) {
+        log.error("중복 닉네임 생성 실패 : {}", e.getMessage());
+        throw new OAuth2AuthenticationException("닉네임 생성 실패입니다");
+    } catch (Exception e) {
+        log.error("OAuth 사용자 생성 실패 : {}", e.getMessage());
+        throw new OAuth2AuthenticationException("사용자 생성 실패입니다");
+    }

위 변경을 통해 닉네임 생성 실패 시에도 OAuth2 필터 단계에서 일관된 예외 처리 흐름이 보장됩니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String nickname = userDomainService.generateUniqueNickname();
User newUser = User.googleUser(oAuth2Response.getEmail(), username, nickname);
log.info("newUser: {} 새로운 유저", newUser);
if (findUser == null) {
try {
String nickname = userDomainService.generateUniqueNickname();
User newUser = User.googleUser(oAuth2Response.getEmail(), username, nickname);
log.info("newUser: {} 새로운 유저", newUser);
userRepository.createUser(newUser);
} catch (IllegalStateException e) {
log.error("중복 닉네임 생성 실패 : {}", e.getMessage());
throw new OAuth2AuthenticationException("닉네임 생성 실패입니다");
} catch (Exception e) {
log.error("OAuth 사용자 생성 실패 : {}", e.getMessage());
throw new OAuth2AuthenticationException("사용자 생성 실패입니다");
}
}
🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java
around lines 54 to 64, the call to userDomainService.generateUniqueNickname()
can throw IllegalStateException which is not currently caught and wrapped as
OAuth2AuthenticationException. To fix this, expand the try block to include the
nickname generation call and catch IllegalStateException separately, then wrap
and rethrow it as an OAuth2AuthenticationException. This ensures consistent
exception handling in the OAuth2 filter layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

합당한데?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pokerbearkr 말씀해주신 대로 예외 매핑을 적용하도록 하겠습니다. 아래 계획대로 CustomOAuth2UserService 에 try-catch 블록을 확장하여 IllegalStateExceptionOAuth2AuthenticationException 으로 변환하는 코드를 반영하겠습니다. 확인 후 진행해도 괜찮을지 알려주세요!

• CustomOAuth2UserService 의 findUser == null 분기 내 로직을 try-catch 블록으로 감싸고,
IllegalStateException 을 별도로 잡아 OAuth2AuthenticationException("닉네임 생성 실패입니다") 로 래핑
→ OAuth2 필터 단계에서 일관된 예외 처리 보장

</plan_code_changes>


⏳ Preparing changes...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A summary of the edits CodeRabbit can apply:

CustomOAuth2UserService.java will be updated so that nickname generation and user creation are executed inside a try-catch block which converts IllegalStateException to an OAuth2AuthenticationException, ensuring consistent OAuth error handling.

A plan of each step that CodeRabbit will take in order to execute the edits for each file (with inline rough draft snippets):

✏️ src/main/java/org/ezcode/codetest/domain/user/service/CustomOAuth2UserService.java

Delete the current block spanning lines 55-63 (the standalone nickname generation, new User creation, and single Exception catch).


Insert the following replacement starting at line 55:\njava\n try {\n String nickname = userDomainService.generateUniqueNickname();\n User newUser = User.googleUser(oAuth2Response.getEmail(), username, nickname);\n log.info(\"newUser: {} 새로운 유저\", newUser);\n userRepository.createUser(newUser);\n } catch (IllegalStateException e) {\n log.error(\"중복 닉네임 생성 실패 : {}\", e.getMessage());\n throw new OAuth2AuthenticationException(\"닉네임 생성 실패입니다\");\n } catch (Exception e) {\n log.error(\"OAuth 사용자 생성 실패 : {}\", e.getMessage());\n throw new OAuth2AuthenticationException(\"사용자 생성 실패입니다\");\n }\n


No additional imports are necessary because IllegalStateException is part of java.lang; ensure existing imports remain untouched.


Re-compile and run the OAuth flow (or unit tests that simulate nickname collision) to verify IllegalStateException is now translated into OAuth2AuthenticationException and handled uniformly by the filter layer.

  • ✅ Create PR with these edits
  • 📌 Create commit in current branch
  • 📋 Get copyable edits

try {
userRepository.createUser(newUser);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,70 +1,90 @@
package org.ezcode.codetest.domain.user.service;
package org.ezcode.codetest.domain.user.service;

import org.ezcode.codetest.domain.user.exception.AuthException;
import org.ezcode.codetest.domain.user.exception.AuthExceptionCode;
import org.ezcode.codetest.domain.user.model.entity.User;
import org.ezcode.codetest.domain.user.repository.UserRepository;
import org.ezcode.codetest.infrastructure.security.jwt.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.ezcode.codetest.domain.user.exception.AuthException;
import org.ezcode.codetest.domain.user.exception.AuthExceptionCode;
import org.ezcode.codetest.domain.user.model.entity.User;
import org.ezcode.codetest.domain.user.model.enums.Adjective;
import org.ezcode.codetest.domain.user.model.enums.Noun;
import org.ezcode.codetest.domain.user.repository.UserRepository;
import org.ezcode.codetest.infrastructure.security.jwt.PasswordEncoder;
import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class UserDomainService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class UserDomainService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private static final java.util.Random RANDOM = new java.util.Random();

public void checkEmailUnique(String email) {
if (userRepository.findByEmail(email).isPresent()) {
throw new AuthException(AuthExceptionCode.ALREADY_EXIST_USER);
public void checkEmailUnique(String email) {
if (userRepository.findByEmail(email).isPresent()) {
throw new AuthException(AuthExceptionCode.ALREADY_EXIST_USER);
}
}
}

public void createUser(User user) {
userRepository.createUser(user);
}
public void createUser(User user) {
userRepository.createUser(user);
}

public User getUser(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new AuthException(AuthExceptionCode.USER_NOT_FOUND));
}
public User getUser(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new AuthException(AuthExceptionCode.USER_NOT_FOUND));
}

public User getUserById(Long id) {
return userRepository.findUserById(id)
.orElseThrow(()->new AuthException(AuthExceptionCode.USER_NOT_FOUND));
}
public User getUserById(Long id) {
return userRepository.findUserById(id)
.orElseThrow(()->new AuthException(AuthExceptionCode.USER_NOT_FOUND));
}

public void userPasswordCheck(String email, String password) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new AuthException(AuthExceptionCode.USER_NOT_FOUND));;
public void userPasswordCheck(String email, String password) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new AuthException(AuthExceptionCode.USER_NOT_FOUND));;

if (!passwordEncoder.matches(password, user.getPassword())) {
throw new AuthException(AuthExceptionCode.PASSWORD_NOT_MATCH);
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new AuthException(AuthExceptionCode.PASSWORD_NOT_MATCH);
}
}
}

public String encodePassword(String password) {
return passwordEncoder.encode(password);
}
public String encodePassword(String password) {
return passwordEncoder.encode(password);
}

public User getOAuthUser(String email, String provider) {
return userRepository.findByEmailAndProvider(email, provider);
}
public User getOAuthUser(String email, String provider) {
return userRepository.findByEmailAndProvider(email, provider);
}

public void passwordComparison(String newPassword, String oldPassword) {
if (passwordEncoder.matches(newPassword, oldPassword)) {
throw new AuthException(AuthExceptionCode.PASSWORD_IS_SAME);
public void passwordComparison(String newPassword, String oldPassword) {
if (passwordEncoder.matches(newPassword, oldPassword)) {
throw new AuthException(AuthExceptionCode.PASSWORD_IS_SAME);
}
}

public void isDeletedUser(User user) {
if (user.isDeleted()) {
throw new AuthException(AuthExceptionCode.ALREADY_WITHDRAW_USER);
}
}

public String generateUniqueNickname() {
for (int i = 0; i < 10000000; i++) {
String nickname = generateRandomNickname();
if(!userRepository.existsByNickname(nickname)) {
return nickname;
}
}
throw new IllegalStateException("중복된 닉네임 생성 불가");
}
}

public void isDeletedUser(User user) {
if (user.isDeleted()) {
throw new AuthException(AuthExceptionCode.ALREADY_WITHDRAW_USER);
private static String generateRandomNickname() {
Adjective adjective = Adjective.values()[RANDOM.nextInt(Adjective.values().length)];
Noun noun = Noun.values()[RANDOM.nextInt(Noun.values().length)];
int number = RANDOM.nextInt(1000);
return adjective.name() + noun.name() + number;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface UserJpaRepository extends JpaRepository<User, Long> {
User findUserByEmail(String email);

User findUserByEmailAndAuthType(String email, AuthType authType);

boolean existsByNickname(String nickname);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public User findByEmailAndProvider(String email, String provider) {
return userJpaRepository.findUserByEmailAndAuthType(email, AuthType.from(provider));
}

@Override
public boolean existsByNickname(String nickname) {
return userJpaRepository.existsByNickname(nickname);
}

}