-
Notifications
You must be signed in to change notification settings - Fork 44
[volume-1] 회원가입, 내 정보 조회, 비밀번호 수정 기능 구현 #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: juoklee
Are you sure you want to change the base?
Changes from all commits
efc51f0
2ff2f5e
08b5522
d300c29
9e97816
1798926
86fe2ec
f0e6eda
ae66d53
48123b3
fa2ad35
c0dcfd8
e4bb0f9
85c1562
ab309b2
d1b4d52
d1c0757
2730d67
235c0d5
9f6574d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.loopers.application.member; | ||
|
|
||
| import com.loopers.domain.member.Member; | ||
| import com.loopers.domain.member.MemberService; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Component | ||
| public class MemberFacade { | ||
|
|
||
| private final MemberService memberService; | ||
|
|
||
| public MemberInfo register(String loginId, String rawPassword, String name, | ||
| LocalDate birthDate, String email) { | ||
| Member member = memberService.register(loginId, rawPassword, name, birthDate, email); | ||
| return MemberInfo.from(member); | ||
| } | ||
|
|
||
| public MemberInfo getMe(Member authenticatedMember) { | ||
| return MemberInfo.fromWithMaskedName(authenticatedMember); | ||
| } | ||
|
|
||
| public void changePassword(Member authenticatedMember, String currentPassword, String newPassword) { | ||
| memberService.changePassword(authenticatedMember, currentPassword, newPassword); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.loopers.application.member; | ||
|
|
||
| import com.loopers.domain.member.Member; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| public record MemberInfo(String loginId, String name, LocalDate birthDate, String email) { | ||
| public static MemberInfo from(Member member) { | ||
| return new MemberInfo( | ||
| member.getLoginId(), | ||
| member.getName(), | ||
| member.getBirthDate(), | ||
| member.getEmail() | ||
| ); | ||
| } | ||
|
|
||
| public static MemberInfo fromWithMaskedName(Member member) { | ||
| return new MemberInfo( | ||
| member.getLoginId(), | ||
| maskName(member.getName()), | ||
| member.getBirthDate(), | ||
| member.getEmail() | ||
| ); | ||
| } | ||
|
|
||
| private static String maskName(String name) { | ||
| if (name == null || name.isEmpty()) { | ||
| return ""; | ||
| } | ||
| if (name.length() == 1) { | ||
| return "*"; | ||
| } | ||
| return name.substring(0, name.length() - 1) + "*"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| import com.loopers.domain.BaseEntity; | ||
| import com.loopers.support.error.CoreException; | ||
| import com.loopers.support.error.ErrorType; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.Table; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| @Entity | ||
| @Table(name = "member") | ||
| public class Member extends BaseEntity { | ||
|
|
||
| @Column(unique = true, nullable = false) | ||
| private String loginId; | ||
| private String password; | ||
| private String name; | ||
| private LocalDate birthDate; | ||
| private String email; | ||
|
|
||
| protected Member() {} | ||
|
|
||
| private Member(String loginId, String password, String name, | ||
| LocalDate birthDate, String email) { | ||
| this.loginId = loginId; | ||
| this.password = password; | ||
| this.name = name; | ||
| this.birthDate = birthDate; | ||
| this.email = email; | ||
| } | ||
|
|
||
| public static Member create(String loginId, String rawPassword, | ||
| String name, LocalDate birthDate, | ||
| String email, PasswordEncoder encoder) { | ||
| validateNotBlank(loginId, "로그인ID는 필수입니다."); | ||
| validateNotBlank(rawPassword, "비밀번호는 필수입니다."); | ||
| validateNotBlank(name, "이름은 필수입니다."); | ||
| validateNotNull(birthDate, "생년월일은 필수입니다."); | ||
| validateNotBlank(email, "이메일은 필수입니다."); | ||
|
|
||
| validateLoginId(loginId); | ||
| validatePassword(rawPassword, birthDate); | ||
| String normalizedName = normalizeName(name); | ||
| validateName(normalizedName); | ||
| validateEmail(email); | ||
|
|
||
| String encodedPassword = encoder.encode(rawPassword); | ||
| return new Member(loginId, encodedPassword, normalizedName, birthDate, email); | ||
| } | ||
|
Comment on lines
34
to
51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 비밀번호 변경/생성 입력값 null 가드가 필요하다. 수정 예시 public static Member create(String loginId, String rawPassword,
String name, LocalDate birthDate,
String email, PasswordEncoder encoder) {
+ if (encoder == null) {
+ throw new CoreException(ErrorType.BAD_REQUEST, "비밀번호 인코더가 필요합니다.");
+ }
validateLoginId(loginId);
validatePassword(rawPassword, birthDate);
String normalizedName = normalizeName(name);
validateName(normalizedName);
validateEmail(email);
String encodedPassword = encoder.encode(rawPassword);
return new Member(loginId, encodedPassword, normalizedName, birthDate, email);
}
public void changePassword(String currentPassword, String newRawPassword,
PasswordEncoder encoder) {
+ if (currentPassword == null || newRawPassword == null || encoder == null) {
+ throw new CoreException(ErrorType.BAD_REQUEST, "비밀번호 입력값이 누락되었습니다.");
+ }
// 현재 비밀번호 확인
if (!encoder.matches(currentPassword, this.password)) {
throw new CoreException(ErrorType.BAD_REQUEST, "현재 비밀번호가 일치하지 않습니다.");
}추가 테스트로 currentPassword/newRawPassword/encoder가 null일 때 BAD_REQUEST가 발생하는지 단위 테스트를 추가한다. Also applies to: 83-97 🤖 Prompt for AI Agents |
||
|
|
||
| private static void validateNotNull(Object value, String message) { | ||
| if (value == null) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, message); | ||
| } | ||
| } | ||
|
|
||
| private static void validateNotBlank(String value, String message) { | ||
| if (value == null || value.isBlank()) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, message); | ||
| } | ||
| } | ||
|
|
||
| private static void validatePassword(String password, LocalDate birthDate) { | ||
| if (password.length() < 8 || password.length() > 16) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "비밀번호는 8~16자여야 합니다."); | ||
| } | ||
| if (!password.matches("^[a-zA-Z0-9!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?]+$")) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "비밀번호는 영문 대소문자, 숫자, 특수문자만 허용됩니다."); | ||
| } | ||
| String birthDateStr = birthDate.toString().replace("-", ""); // 19900115 | ||
| if (password.contains(birthDateStr)) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "비밀번호에 생년월일을 포함할 수 없습니다."); | ||
| } | ||
| } | ||
|
|
||
| private static void validateLoginId(String loginId) { | ||
| if (!loginId.matches("^[a-zA-Z0-9]+$")) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "로그인ID는 영문과 숫자만 허용됩니다."); | ||
| } | ||
| } | ||
|
|
||
| private static String normalizeName(String name) { | ||
| return name.trim().replaceAll("\\s+", " "); | ||
| } | ||
|
|
||
| private static void validateName(String name) { | ||
| boolean isKorean = name.matches("^[가-힣]+$"); | ||
| boolean isEnglish = name.matches("^[a-zA-Z]+( [a-zA-Z]+)*$"); | ||
|
|
||
| if (!isKorean && !isEnglish) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "이름은 한글만 또는 영문만 허용됩니다."); | ||
| } | ||
| } | ||
|
|
||
| private static void validateEmail(String email) { | ||
| if (!email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "올바른 이메일 형식이 아닙니다."); | ||
| } | ||
| } | ||
|
Comment on lines
34
to
101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 필수 입력 null 처리 누락으로 NPE 위험 🔧 수정 예시 public static Member create(String loginId, String rawPassword,
String name, LocalDate birthDate,
String email, PasswordEncoder encoder) {
+ if (loginId == null || rawPassword == null || name == null || birthDate == null || email == null) {
+ throw new CoreException(ErrorType.BAD_REQUEST, "필수 입력값이 누락되었습니다.");
+ }
validateLoginId(loginId);
validatePassword(rawPassword, birthDate);
String normalizedName = normalizeName(name);
validateName(normalizedName);
validateEmail(email);
@@
private static void validatePassword(String password, LocalDate birthDate) {
+ if (password == null || birthDate == null) {
+ throw new CoreException(ErrorType.BAD_REQUEST, "비밀번호 또는 생년월일이 누락되었습니다.");
+ }
if (password.length() < 8 || password.length() > 16) {
throw new CoreException(ErrorType.BAD_REQUEST, "비밀번호는 8~16자여야 합니다.");
}As per coding guidelines: null 처리, 방어적 복사, 불변성, equals/hashCode/toString 구현 안정성을 점검한다다. 🤖 Prompt for AI Agents |
||
|
|
||
| public void changePassword(String currentPassword, String newRawPassword, | ||
| PasswordEncoder encoder) { | ||
| validateNotBlank(currentPassword, "현재 비밀번호는 필수입니다."); | ||
| validateNotBlank(newRawPassword, "새 비밀번호는 필수입니다."); | ||
|
|
||
| // 현재 비밀번호 확인 | ||
| if (!encoder.matches(currentPassword, this.password)) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "현재 비밀번호가 일치하지 않습니다."); | ||
| } | ||
| // 새 비밀번호가 현재와 동일한지 확인 | ||
| if (encoder.matches(newRawPassword, this.password)) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "새 비밀번호는 현재 비밀번호와 달라야 합니다."); | ||
| } | ||
| // 새 비밀번호 규칙 검증 | ||
| validatePassword(newRawPassword, this.birthDate); | ||
| // 비밀번호 변경 | ||
| this.password = encoder.encode(newRawPassword); | ||
| } | ||
|
|
||
| // Getter | ||
| public String getLoginId() { | ||
| return loginId; | ||
| } | ||
|
|
||
| public String getPassword() { | ||
| return password; | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| public LocalDate getBirthDate() { | ||
| return birthDate; | ||
| } | ||
|
|
||
| public String getEmail() { | ||
| return email; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface MemberReader { | ||
| boolean existsByLoginId(String loginId); | ||
| Optional<Member> findByLoginId(String loginId); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| public interface MemberRepository { | ||
| Member save(Member member); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| import com.loopers.support.error.CoreException; | ||
| import com.loopers.support.error.ErrorType; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Component | ||
| public class MemberService { | ||
|
|
||
| private final MemberReader memberReader; | ||
| private final MemberRepository memberRepository; | ||
| private final PasswordEncoder passwordEncoder; | ||
|
|
||
| public Member register(String loginId, String rawPassword, String name, | ||
| LocalDate birthDate, String email) { | ||
| if (memberReader.existsByLoginId(loginId)) { | ||
| throw new CoreException(ErrorType.BAD_REQUEST, "이미 존재하는 로그인ID입니다."); | ||
| } | ||
|
|
||
| Member member = Member.create(loginId, rawPassword, name, birthDate, email, passwordEncoder); | ||
| return memberRepository.save(member); | ||
|
Comment on lines
+18
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -type f -name "MemberService.java" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 163 🏁 Script executed: find . -type f -name "Member.java" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 156 🏁 Script executed: fd -t f "MemberService" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 242 🏁 Script executed: wc -l ./apps/commerce-api/src/main/java/com/loopers/domain/member/MemberService.java
wc -l ./apps/commerce-api/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 242 🏁 Script executed: cat -n ./apps/commerce-api/src/main/java/com/loopers/domain/member/MemberService.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1427 🏁 Script executed: cat -n ./apps/commerce-api/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 4823 🏁 Script executed: find . -type f -name "MemberRepository.java" -o -name "MemberReader.java"Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 244 🏁 Script executed: grep -r "DataIntegrityViolationException" ./apps/commerce-api/src --include="*.java" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: cat -n ./apps/commerce-api/src/test/java/com/loopers/domain/member/MemberServiceTest.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 4987 🏁 Script executed: cat -n ./apps/commerce-api/src/main/java/com/loopers/domain/member/MemberRepository.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 225 🏁 Script executed: cat -n ./apps/commerce-api/src/main/java/com/loopers/domain/member/MemberReader.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 335 🏁 Script executed: grep -r "unique\|@Column\|@Index" ./apps/commerce-api/src/main/java/com/loopers/domain/member --include="*.java"Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: find ./apps/commerce-api -type f -name "*.java" | xargs grep -l "implements MemberRepository\|implements MemberReader" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 351 🏁 Script executed: find ./apps/commerce-api -type f -name "*MemberRepository*.java" -o -name "*MemberReader*.java"Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 428 🏁 Script executed: grep -r "class.*MemberRepository\|class.*MemberReader" ./apps/commerce-api/src --include="*.java"Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 682 🏁 Script executed: cat -n ./apps/commerce-api/src/main/java/com/loopers/infrastructure/member/MemberRepositoryImpl.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 699 🏁 Script executed: cat -n ./apps/commerce-api/src/main/java/com/loopers/infrastructure/member/MemberReaderImpl.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 929 🏁 Script executed: grep -n "Column\|Index\|unique\|constraint" ./apps/commerce-api/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: find ./apps/commerce-api -type f -name "MemberJpaRepository.java"Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 177 🏁 Script executed: cat -n ./apps/commerce-api/src/main/java/com/loopers/infrastructure/member/MemberJpaRepository.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 511 🏁 Script executed: find ./apps/commerce-api -type f \( -name "*.sql" -o -name "*Migration*.java" -o -name "schema*" \) | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: find ./apps -type f -name "*.xml" -o -name "*.sql" | xargs grep -l "member" 2>/dev/null | head -10Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: grep -r "DataIntegrityViolationException\|ConstraintViolationException" ./apps/commerce-api/src/main/java --include="*.java" -A 3 | head -30Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: grep -r "@Transactional\|@Column" ./apps/commerce-api/src/main/java/com/loopers/domain/member --include="*.java"Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: find ./apps/commerce-api -type f -name "application*.yml" -o -name "application*.yaml" | xargs cat 2>/dev/null | grep -A 20 "jpa\|hibernate" | head -30Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 356 동시 가입 요청에서 로그인ID 중복 경쟁 상태 처리 필수 현재 코드는 existsByLoginId 검사 후 저장하는 사이에 다른 요청이 동일 loginId를 저장할 수 있어, 데이터베이스 중복 제약 위반 시 DataIntegrityViolationException이 발생하면 500 에러로 노출된다. 운영 안정성을 위해 다음을 필수 처리해야 한다:
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| public void changePassword(Member member, String currentPassword, String newPassword) { | ||
| member.changePassword(currentPassword, newPassword, passwordEncoder); | ||
| memberRepository.save(member); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.loopers.domain.member; | ||
|
|
||
| public interface PasswordEncoder { | ||
| String encode(String rawPassword); | ||
| boolean matches(String rawPassword, String encodedPassword); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.loopers.infrastructure.member; | ||
|
|
||
| import com.loopers.domain.member.Member; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface MemberJpaRepository extends JpaRepository<Member, Long> { | ||
| boolean existsByLoginId(String loginId); | ||
| Optional<Member> findByLoginId(String loginId); | ||
|
Comment on lines
+8
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: fd -type f -name "MemberJpaRepository.java" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 255 🏁 Script executed: fd -type f -name "Member.java" | grep -E "(entity|domain|model)" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 255 🏁 Script executed: # Member 엔티티 파일 찾기
fd -type f -name "*Member*.java" | head -30Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 255 🏁 Script executed: git ls-files | grep -E "MemberJpaRepository|Member\.java" | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 245 🏁 Script executed: # 저장소 구조 확인
find . -type f -name "Member*.java" 2>/dev/null | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1338 🏁 Script executed: cat -n apps/commerce-api/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 4823 🏁 Script executed: cat -n apps/commerce-api/src/main/java/com/loopers/infrastructure/member/MemberJpaRepository.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 511 🏁 Script executed: find . -type f -name "BaseEntity.java" | head -5 | xargs cat -nRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 2291 🏁 Script executed: find . -type f \( -name "V*.sql" -o -name "*migration*" \) | grep -i member | head -10Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: find . -type d \( -name "db" -o -name "migration" -o -name "flyway" \) 2>/dev/nullRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: find . -type f -name "*.sql" 2>/dev/null | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: find . -path "*/resources/db*" -o -path "*/resources/migration*" 2>/dev/null | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: # Member entity에서 `@Index`, `@Column`(unique) 검색
rg -n "@Index|@Column.*unique" apps/commerce-api/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: cat -n apps/commerce-api/src/main/java/com/loopers/infrastructure/member/MemberRepositoryImpl.java | head -50Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 699 🏁 Script executed: cat -n apps/commerce-api/src/main/java/com/loopers/infrastructure/member/MemberReaderImpl.java | head -50Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 929 🏁 Script executed: find . -type f -name "schema*.sql" -o -name "application*.yml" -o -name "application*.yaml" 2>/dev/null | head -10Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 256 🏁 Script executed: cat apps/commerce-api/src/main/resources/application.ymlRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1044 🏁 Script executed: cat apps/commerce-api/src/main/resources/jpa.ymlRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 161 🏁 Script executed: find . -name "jpa.yml" 2>/dev/nullRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 125 🏁 Script executed: ls -la apps/commerce-api/src/main/resources/Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 260 🏁 Script executed: cat ./modules/jpa/src/main/resources/jpa.ymlRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 2231 🏁 Script executed: find . -type f \( -name "*.sql" -o -name "*.ddl" \) 2>/dev/null | head -20Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 67 🏁 Script executed: # Member entity 전체 어노테이션 다시 확인
rg -A 20 "^@Entity" apps/commerce-api/src/main/java/com/loopers/domain/member/Member.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 611 loginId 컬럼에 인덱스와 유니크 제약조건을 추가해야 한다. existsByLoginId와 findByLoginId는 로그인 및 회원가입 시 loginId 기반 조회를 수행하며, 현재 loginId 필드에 Member 엔티티의 loginId 필드에 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.loopers.infrastructure.member; | ||
|
|
||
| import com.loopers.domain.member.Member; | ||
| import com.loopers.domain.member.MemberReader; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Component | ||
| public class MemberReaderImpl implements MemberReader { | ||
| private final MemberJpaRepository memberJpaRepository; | ||
|
|
||
| @Override | ||
| public boolean existsByLoginId(String loginId) { | ||
| return memberJpaRepository.existsByLoginId(loginId); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Member> findByLoginId(String loginId) { | ||
| return memberJpaRepository.findByLoginId(loginId); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.loopers.infrastructure.member; | ||
|
|
||
| import com.loopers.domain.member.Member; | ||
| import com.loopers.domain.member.MemberRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Component | ||
| public class MemberRepositoryImpl implements MemberRepository { | ||
| private final MemberJpaRepository memberJpaRepository; | ||
|
|
||
| @Override | ||
| public Member save(Member member) { | ||
| return memberJpaRepository.save(member); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.loopers.infrastructure.member; | ||
|
|
||
| import com.loopers.domain.member.PasswordEncoder; | ||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class PasswordEncoderImpl implements PasswordEncoder { | ||
| private final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); | ||
|
|
||
| @Override | ||
| public String encode(String rawPassword) { | ||
| return bCryptPasswordEncoder.encode(rawPassword); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean matches(String rawPassword, String encodedPassword) { | ||
| return bCryptPasswordEncoder.matches(rawPassword, encodedPassword); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 1271
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 242
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 4538
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 747
🌐 Web query:
Spring Boot 3.4.4 spring-boot-starter-security version CVE💡 Result:
Spring Boot 3.4.4’s
spring-boot-starter-securitypulls in Spring Security 6.4.4 (via the Spring Boot BOM). [1]CVE context for Spring Security 6.4.4
So, if you’re on Boot 3.4.4, you’re on Spring Security 6.4.4 (includes fixes for CVE-2025-22223/22228, but is impacted by CVE-2025-22234). [1][2][3]
What to do
References:
[1] (github.com)
[2] (spring.io)
[3] (spring.io)
Citations:
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 233
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 110
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 67
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 474
🏁 Script executed:
Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java
Length of output: 121
Spring Security 6.4.4의 CVE-2025-22234 취약점 해결이 필수다.
Spring Boot 3.4.4 BOM에 의해 Spring Security 6.4.4가 관리되고 있으나, 해당 버전은 CVE-2025-22234에 영향을 받는다. 이 취약점은 인증/인가 처리 과정에서 발생하는 보안 결함이며, 상용 환경에서 미해결 시 인증 우회 등의 위험에 노출된다.
수정안으로
dependencyManagement블록에서 Spring Security를 6.4.5 이상으로 명시적으로 오버라이드한다.이후 의존성 취약점 스캔 태스크를 CI에 포함하고, 회원가입/로그인/비밀번호 변경 API의 인증/인가 관련 스모크 테스트를 추가한다.
🤖 Prompt for AI Agents