본문 바로가기
[Project] CafeHub

[CafeHub] 코드 분석 및 이슈 기록 - 아이디 찾기 / 비밀번호 찾기

by heosj 2024. 1. 5.

✔️ 주요 코드 중심 분석 ✔️


아이디 찾기

Front

// 아이디 찾기 정보 입력 페이지
axios.post(`${url}/searchId`, { name: data.name, phone: data.phone }) // 1
.then((res) => {
  Toast('success', '결과 페이지로 이동합니다')
  setTimeout(() => {
    navigate('/searchIdResult', { state: { result: res.data.id } }); // 2
  }, 1500);
})
.catch((err) => {
    Toast('error', err.response.data)
})
// 아이디 찾기 결과 페이지
const location = useLocation();
const searchId = location.state.result; // 3
  1.  searchId 엔드포인트로 이름과 전화번호를 담아 POST 요청 전달
  2. 응답으로 받은 데이터를 useNavigate()로 페이지를 이동시키며 파라미터를 전달
  3. 결과 페이지에서 useLocation()으로 파라미터를 취득하여 결과 노출

Back

@PostMapping("/searchId")
public ResponseEntity<Object> idSearch(@RequestBody SearchIdDto searchIdDto) {
    try {
        String name = searchIdDto.getName();
        String phone = searchIdDto.getPhone();
        Member member = memberService.searchId(name, phone); // 1
        SearchIdDto id = SearchIdDto.searchIdToDto(member); // 2
        return new ResponseEntity<>(id, HttpStatus.OK);
    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
    }
}
public Member searchId(String name, String phone) throws Exception {
    Member member = memberRepository.findByNameAndPhone(name, phone); // 3
    if(member == null) throw new Exception("회원정보가 일치하지 않습니다.");
    return member;
}
  1. 클라이언트에서 전달 받은 데이터를 통해 서비스 레이어로 해당 회원 정보 요청
  2. Member 객체를 DTO로 변환하여 클라이언트에 반환 
  3. 데이터베이스에서 이름과 전화번호를 이용하여 회원 정보를 검색 

비밀번호 찾기/재설정

Front

axios.post(`${url}/searchPw`, { id: data.id, phone: data.phone }) // 1
.then((res) => {
    if (res.data === true){ // 2 
      const random = Math.floor(Math.random() * 9000) + 1000;
      setRandomCode(random);
      console.log(random);
      axios.get(`${url}/check/sendSMS?phone=${data.phone}&code=${random}`)
      .then((res) => {
        Toast('success', '인증번호가 발송되었습니다')
          setPhoneAuth(true);
      })
    } else {
      Toast('error', '일치하는 회원 정보가 없습니다')
    }
})
.catch((error) => {
  Toast('error', error.response.data)
});
const handleSubmit = (e) => {
    e.preventDefault(); // 3
	// 유효성 검사 생략
    axios.put(`${url}/resetPw/${id}`, { password: data.password }) // 4
    .then((res) => {
        Toast('error', '완료되었습니다\n로그인 페이지로 이동합니다')
        setTimeout(() => {
            navigate('/login');
        }, 1500);
    })
    .catch((error) => {
        Toast('error', '실패했습니다\n다시 시도해주세요')
    })
}
  1.  searchPw 엔드포인트로 아이디와 전화번호를 담아 POST 요청 전달
  2. 응답 데이터가 true인 경우(회원정보가 일치하는 경우) 랜덤 코드를 생성하여 휴대폰 인증번호 발송을 위한 요청 전달
  3. 유효성 검사를 위해 e.preventDetault()를 이용하여 submit을 방지
  4. resetPw/{id} 엔드포인트로 아이디와 변경할 비밀번호를 담아 PUT 요청 전달하여 비밀번호 재설정 완료

Back

@PostMapping("/searchPw")
public ResponseEntity<Object> pwSearch(@RequestBody SearchPwDto searchPwDto) {
    try {
        String id = searchPwDto.getId();
        String phone = searchPwDto.getPhone();
        boolean result = memberService.searchPw(id, phone); // 1
        return new ResponseEntity<>(result, HttpStatus.OK);
    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
    }
}

@PutMapping("/resetPw/{id}")
public ResponseEntity<Object> changePassword(@PathVariable String id, @RequestBody SearchPwDto searchPwDto) {
    try {
        memberService.changePw(id, searchPwDto.getPassword()); // 1
        return new ResponseEntity<>(true, HttpStatus.OK);
    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }
}
public boolean searchPw(String id, String phone) throws Exception {
    Member member = memberRepository.findById(id); // 2
    if (member != null && member.getPhone().equals(phone)) {
        return true;
    } else {
        return false;
    }
}

public void changePw(String id, String newPassword) throws Exception {
    Member member = memberRepository.findById(id); // 2
    if(member != null) {
        member.changePassword(bCryptPasswordEncoder.encode(newPassword)); // 3
        memberRepository.save(member);
    } else {
        throw new Exception("회원정보가 일치하지 않습니다.");
    }
}
  1. 클라이언트에서 전달 받은 데이터를 통해 서비스 레이어로 해당 회원 정보 요청
  2. id를 통해 회원 정보를 담은 객체 저장
  3. 회원정보가 있는 경우 BCryptPasswordEncoder를 사용하여 새로운 비밀번호를 해싱하고 데이터베이스에 저장

휴대폰 인증번호 발송

CoolSMS를 활용하여 휴대폰 인증번호를 발송

public class PhoneCodeServiceImpl implements PhoneCodeService {
    @Value("${send-phone-key}")
    private String apiKey;
    @Value("${send-phone-secret-key}")
    private String secretKey;
    @Value("${soobin-phone-number}")
    private String fromNumber;
    private DefaultMessageService messageService;

    public PhoneCodeServiceImpl() {}

    @PostConstruct
    public void init() {
        this.messageService = NurigoApp.INSTANCE.initialize(apiKey, secretKey, "https://api.coolsms.co.kr");
    }

    @Override
    public SingleMessageSentResponse sendPhoneCode(String phone, String code) throws Exception {
        Message message = new Message();
        message.setFrom(fromNumber); // 발신번호
        message.setText("[CafeHub] 인증번호 : " + code + "\n타인 유출로 인한 피해 주의");
        message.setTo(phone);
        return this.messageService.sendOne(new SingleMessageSendingRequest(message));
    }
}
  1. @Value 어노테이션을 통해 외부 프로퍼티 파일에서 값을 주입
  2. @PostConstruct 어노테이션을 통해 클래스가 초기화될 때 실행하도록 설정 
발생 에러
org.springframework.beans.factory.
UnsatisfiedDependencyException
: Error creating bean with name 'phoneCodeController': Unsatisfied dependency expressed through field 'phoneCodeService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name  'phoneCodeServiceImpl' defined in file 

발생 원인
PhoneCodeServiceImpl 객체를 초기화하는 중 @Value로 주입되는 값들이 아직 초기화되지 않은 상태에서 NurigoApp.INSTANCE.initialize() 메서드가 호출되어 발생한 문제
Spring은 @Value로 주입되는 값을 객체 생성 과정에서 초기화하는데, 생성자 초기화를 시도할 때 해당 값들이 아직 주입되지 않아 값을 참조하지 못하는 경우

해결 방법
@PostConstruct 어노테이션을 사용하여 객체가 생성된 이후에 초기화 되도록 설정하여 객체 생성 과정에서 @Value로 주입된 프로퍼티 값을 사용할 수 있도록 함
따라서, 해당 값들이 모두 주입된 이후 초기화 메소드가 실행되어 문제 해결

 

휴대폰 인증번호 발송 결과