Conversation
There was a problem hiding this comment.
Pull Request Overview
이 PR은 소셜 로그인에서 자체 이메일/비밀번호 기반 인증 시스템으로 전환하는 기능을 구현합니다. 회원가입 시 설문조사와 회원 정보를 동시에 등록하고, BCrypt를 사용한 비밀번호 해싱 보안을 적용합니다.
- 자체 이메일/비밀번호 회원가입 및 로그인 시스템 구현
- 회원가입과 설문조사 통합 처리
- BCrypt 기반 비밀번호 암호화 적용
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| ErrorCode.java | 로그인 관련 에러 코드 추가 (중복 이메일, 잘못된 인증 정보) |
| SecurityConfig.java | 인증 엔드포인트 허용 및 PasswordEncoder 빈 추가 |
| MemberSignupService.java | 회원 추가 정보 업데이트 메서드 파라미터 확장 |
| LocalAuthService.java | 로컬 인증 서비스 구현 (회원가입, 로그인, 이메일 중복 확인) |
| LoginResponse.java | 로그인 응답 DTO 추가 |
| LocalLoginRequest.java | 로그인 요청 DTO 추가 |
| AuthController.java | 인증 관련 REST API 엔드포인트 구현 |
| Member.java | Member 엔티티에 LOCAL 로그인 타입 추가 및 빌더 패턴 적용 |
Comments suppressed due to low confidence (1)
src/main/java/com/arom/with_travel/global/config/SecurityConfig.java:1
- 두 개의 빈이 동일한 BCryptPasswordEncoder 인스턴스를 생성하고 있어 중복됩니다. 하나의 빈만 유지하고 다른 하나는 제거하거나, 기존 bCryptPasswordEncoder() 메서드를 passwordEncoder()로 이름을 변경하는 것을 권장합니다.
package com.arom.with_travel.global.config;
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| Member m = memberRepository.findByEmail(req.getEmail()) | ||
| .orElseThrow(() -> BaseException.from(ErrorCode.MEMBER_NOT_FOUND)); | ||
|
|
||
| if (m.getPassword() == null || !passwordEncoder.matches(req.getPassword(), m.getPassword())) { |
There was a problem hiding this comment.
비밀번호가 null인 경우와 비밀번호가 일치하지 않는 경우를 동일한 에러 메시지로 처리하는 것은 좋지만, 사용자가 존재하지 않는 경우와 비밀번호가 틀린 경우도 동일한 에러 코드(INVALID_CREDENTIALS)로 처리하여 계정 열거 공격을 방지하는 것을 권장합니다.
| Member m = memberRepository.findByEmail(req.getEmail()) | |
| .orElseThrow(() -> BaseException.from(ErrorCode.MEMBER_NOT_FOUND)); | |
| if (m.getPassword() == null || !passwordEncoder.matches(req.getPassword(), m.getPassword())) { | |
| Member m = memberRepository.findByEmail(req.getEmail()).orElse(null); | |
| if (m == null || m.getPassword() == null || !passwordEncoder.matches(req.getPassword(), m.getPassword())) { |
| public void updateExtraInfo(String nickname, LocalDate birth, Gender gender, String introduction, | ||
| String email, String password, String name, String phone) { | ||
| this.nickname = nickname; | ||
| this.birth = birth; | ||
| this.gender = gender; | ||
| this.introduction = introduction; | ||
| this.email = email; | ||
| this.password = password; | ||
| this.name = name; | ||
| this.phone = phone; | ||
| } |
There was a problem hiding this comment.
updateExtraInfo 메서드가 이제 기본 정보(email, password, name, phone)까지 업데이트하므로 메서드명이 부적절합니다. updateMemberInfo 또는 updateAllInfo와 같이 더 명확한 이름으로 변경하는 것을 권장합니다.
| Member m = memberRepository.findByEmail(req.getEmail()) | ||
| .orElseThrow(() -> BaseException.from(ErrorCode.MEMBER_NOT_FOUND)); | ||
|
|
||
| if (m.getPassword() == null || !passwordEncoder.matches(req.getPassword(), m.getPassword())) { |
There was a problem hiding this comment.
로그인 실패 시 MEMBER_NOT_FOUND와 INVALID_CREDENTIALS 두 가지 다른 에러 코드를 사용하면 공격자가 유효한 이메일 주소를 식별할 수 있습니다. 모든 로그인 실패 케이스에 대해 동일한 에러 코드를 사용하는 것을 권장합니다.
| Member m = memberRepository.findByEmail(req.getEmail()) | |
| .orElseThrow(() -> BaseException.from(ErrorCode.MEMBER_NOT_FOUND)); | |
| if (m.getPassword() == null || !passwordEncoder.matches(req.getPassword(), m.getPassword())) { | |
| // Use a dummy password hash to prevent timing attacks and user enumeration | |
| final String DUMMY_PASSWORD_HASH = "$2a$10$7EqJtq98hPqEX7fNZaFWoOa5g5r9Z3rro3yd1y0T5k4bFZ5WD7FZm"; // bcrypt for "dummy_password" | |
| Member m = memberRepository.findByEmail(req.getEmail()).orElse(null); | |
| String passwordHash = (m != null && m.getPassword() != null) ? m.getPassword() : DUMMY_PASSWORD_HASH; | |
| if (m == null || !passwordEncoder.matches(req.getPassword(), passwordHash)) { |
이슈
변경 요약
변경 사항
회원가입 (이메일 + 비밀번호 방식)
로그인 (이메일 + 비밀번호)
이메일 중복 확인
true/false로 알려줍니다. (T: 해당 이메일 사용가능/F: 중복 이메일 존재)보안 처리
변경 근거
infoChecked제공