회원가입 구현 시 백엔드 처리 순서
1. 이메일 중복확인
2. 이메일 인증
3. 닉네임 중복확인
4. 데이터베이스에 저장
1. 이메일 중복확인 + 닉네임 중복확인
- 프론트에서 요청 보내기
axiosURL.post('/member/email/exists', {email:email})
.then(res => {
if (res.data) {
setOnAuthEmail(true)
setPassEmail(true)
sendEmail()
} else {
setPassEmail(false)
setOnAuthEmail(false)
}
}).catch(error => {
console.log(error)
})
중복확인은 단순 조회지만 이메일은 개인정보이기 때문에 그냥 post처리.
RequestBody에 이메일 실어서 백엔드에 요청
- 백엔드 처리
@PostMapping("/email/exists") //이메일인증
public ResponseEntity<Boolean> existsEmail(@RequestBody Map<String,String> body) {
try {
String email = body.get("email");
Optional<MemberDto> member = memberService.getMemberByEmail(email);
if (member.isEmpty()) { //중복확인 통과
return new ResponseEntity<Boolean>(true, HttpStatus.OK);
} else {
return new ResponseEntity<Boolean>(false, HttpStatus.OK);
}
} catch (Exception e) {
return new ResponseEntity<Boolean>(HttpStatus.BAD_REQUEST);
}
}
body에서 이메일을 찾아 memberService에 넘겨줌.
받은 이메일로 memberService는 memberRepository를 통해 데이터베이스에서 멤버를 찾아옴
public interface MemberRepository extends MongoRepository<MemberDto, String> {
public Optional<MemberDto> findByEmail(String email) throws Exception;
public Optional<MemberDto> findByNickname(String nickname) throws Exception;
}
만약 멤버가 존재하지 않으면 중복확인 통과 -> true반환
멤버가 존재하면 중복확인 실패 -> false 반환
- 내려온 응답에 따른 프론트 처리
이메일 인증
준비과정
프론트로부터 이메일 받는 과정은 생략하겠음.
먼저, 메일을 보낼 때 사용할 이메일 정보를 설정해야한다.
구글, 네이버 등등 사용할 사이트의 메일 설정에 들어가서 기본 설정을 바꿔줘야 하는데
이 과정은 구글에 스프링부트 메일보내기 등등으로 검색하면 쉽게 따라할 수 있다.
나는 구글 메일을 사용했는데, 내 실제 이메일, 비밀번호를 하드코딩해 github에 올릴 수 없으니 환경변수로 등록하여
프로젝트 코드 내에서 다루지 않도록 했다.
작업표시줄 시스템 환경 변수 검색 > 시스템 변수 > 새로만들기 > 값 설정
그리고 application.properties에 이메일을 보내기 위한 사용자 정보를 추가했다.
( db등 각종 연결정보를 담는 application.properties 도 .gitignore에 등록하여 github에 올리지 않도록 하는 것이 좋다.)
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=${MAJORS_EMAIL_USERNAME}
spring.mail.password=${MAJORS_EMAIL_PASSWORD}
다음으로 spring에서 제공하는 메일 보내기 기능을 위해서 javaMailSender 의존성을 주입한다.
implementation 'org.springframework.boot:spring-boot-starter-mail'
그리고 javaMailSender를 @Bean으로 등록해준다.
package com.binunu.majors.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.validation.annotation.Validated;
import java.util.Properties;
@Configuration
public class MailConfig {
@Value("${spring.mail.host}")
private String host;
@Value("${spring.mail.port}")
private int port;
@Value("${spring.mail.username}")
private String username;
@Value("${spring.mail.password}")
private String password;
@Bean
public JavaMailSender javaMailSender(){
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
mailSender.setPort(port);
mailSender.setUsername(username);
mailSender.setPassword(password);
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol","smtp");
props.put("mail.smtp.auth","true");
//TSL 설정
props.put("mail.smtp.starttls.enable","true");
props.put("mail.smtp.starttls.required","true");
return mailSender;
}
}
사용할 메일 서버와 포트, 내 이메일과 비밀번호를 가져와서 설정.
여기까지 했으면 준비는 끝났다.
-Controller
package com.binunu.majors.membership.controller;
import com.binunu.majors.membership.service.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("member")
public class EmailController {
private final EmailService emailService;
@Autowired
public EmailController(EmailService emailService){
this.emailService = emailService;
}
@PostMapping("/email/send")
public ResponseEntity<String> sendAuthNumByEmail(@RequestBody Map<String,String> body){
String email = body.get("email");
try{
String code=emailService.sendAuthNumByEmail(email);
return new ResponseEntity<String>(code, HttpStatus.OK);
}catch (Exception e){
e.printStackTrace();
return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
}
}
}
컨트롤러는 그냥 서비스에 이메일을 전달해주는 역할만 함
-Service
package com.binunu.majors.membership.service;
import jakarta.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class EmailServiceImpl implements EmailService {
private final JavaMailSender javaMailSender;
public EmailServiceImpl(JavaMailSender javaMailSender){
this.javaMailSender = javaMailSender;
}
@Value("${spring.mail.host}")
private String host;
//인증번호 생성로직
@Override
public String generateVerificationCode() throws Exception {
int length = 6;
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++){
int index = random.nextInt(characters.length());
sb.append(characters.charAt(index));
}
return sb.toString();
}
@Override
public String sendAuthNumByEmail(String email) throws Exception {
String code = generateVerificationCode();
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message,true,"UTF-8");
helper.setTo(email);
helper.setFrom(host,"전공자들");
helper.setSubject("[전공자들] 회원가입 인증 번호 안내");
String htmlText = "<h2>전공자들 인증 번호 안내</h2>"+
"<br/>"+
"<p>회원님께서 요청하신 전공자들 회원 가입을 위한 이메일 인증 번호를 알려드립니다.</p>"+
"<br/>"+
"<p>아래 인증코드를 복사하여 입력해주시기 바랍니다.</p>"+
"<br/>"+
"<h3>인증코드 : "+code+"</h3>"+
"<br/>";
helper.setText(htmlText, true);
javaMailSender.send(message);
return code;
}
}
service는 두 개의 메소드로 구성되어 있음.
generateVerificationCode() 는 랜덤한 값의 인증 코드를 생성해 주는 함수이고,
sendAuthNumByEmail(String email) 이 메일을 보내는 함수이다.
sendAuthNumByEmail에서 메일을 보내기 위해 MimeMessage객체를 생성한다.
코드의 핵심은 MimeMessage 객체의 to, from, subject, text 에 각각 받는사람, 보내는사람, 메일제목, 메일내용을 담아서 전송하는게 다인데 부가적인 코드들이 많아서 복잡해 보일 수도 있다. 이 함수를 자세히 설명해 보면 다음과 같다.
@Override
public String sendAuthNumByEmail(String email) throws Exception {
String code = generateVerificationCode(); //인증 코드 생성
MimeMessage message = javaMailSender.createMimeMessage(); //전송할 메일 생성
//MimeMessage를 더 쉽게 사용할 수 있도록 해주는 클래스
MimeMessageHelper helper = new MimeMessageHelper(message,true,"UTF-8");
helper.setTo(email); //파라미터로 받아온 받는사람 이메일.
helper.setFrom(host,"전공자들"); //보내는 사람 이메일. *1)
helper.setSubject("[전공자들] 회원가입 인증 번호 안내"); //메일 제목
String htmlText = "<h2>전공자들 인증 번호 안내</h2>"+ // 메일 내용
"<br/>"+
"<p>회원님께서 요청하신 전공자들 회원 가입을 위한 이메일 인증 번호를 알려드립니다.</p>"+
"<br/>"+
"<p>아래 인증코드를 복사하여 입력해주시기 바랍니다.</p>"+
"<br/>"+
"<h3>인증코드 : "+code+"</h3>"+
"<br/>";
helper.setText(htmlText, true); //*2)
javaMailSender.send(message); //메일전송
return code; //프론트에 입력받은 인증번호와 대조하기 위해 code를 리턴해줌.
}
*1)
helper.setFrom(host,"전공자들")
javaMailSender 를 bean등록하는 과정에서 보내는 사람의 정보를 입력했는데 왜 여기서 또 설정하냐면
사실, setFrom을 따로 설정하지 않아도 정상적으로 이메일을 보낼 수 있다.
하지만 실제 메일을 받으면
이런식으로 개인이 보낸 메일처럼 직접적인 이메일 주소로 발송된다.
MimeMesageHelper의 setFrom에 두개의 인자로 첫번째 인자는 발신메일, 두번째 인자로 발신인명을 지정할 수 있다.
*2)
helper.setText(htmlText, true)
메세지 객체는 MimeMessage 말고도 SimpleMailMessage이 있다.
하지만 SimpleMailMessage는 단순한 텍스트만를 전송시킬 수 있는 반면,
MimeMessage는 메일을 단락별로 나눠서 html형식으로 나름 꾸며서 보낼 수 있는 장점이 있다.
하지만 만약 HTML형식의 이메일을 받을 수 없는 클라이언트에서는 작성한 html태그들이 모두 하드코딩되어 보여지기 때문에 MimeMessageHelper에서는 setText()의 두 번째 인자로 true를 설정하면 HTML을 텍스트로 fallback하는 기능을 제공한다.
개발일정 + 우선순위 상 유효기간 설정을 못했는데, 다른 기능을 마무리하며 디테일을 추가할 예정이다...
4. 데이터베이스에 저장
const submitSet = (e) => {
e.preventDefault()
console.log(passEmail, passAuthNum, passPassword, passNickname)
if (!passEmail || !passAuthNum || !passPassword || !passNickname) {
e.preventDefault()
alert("입력한 정보를 다시 확인해주세요")
}
const requestData = {
name : name,
email: email,
password: password,
nickname: nickname,
major: major,
graduate: graduate,
};
axiosURL.post('/member/join',requestData)
.then(res => {
setState('Last')
}).catch(err => {
console.log(err)
})
}
원래는 formData를 사용해 프론트에서 받아오는 로직을 작성했었는데 , 자꾸 에러가 났다.
알고보니 formData는 파일을 업로드 할 때 사용하기 위한 객체라는 것이었다.
그래서 직접 객체를 만들어 post형식으로 body에 담아 전송했다.
memberRepository의 save()를 통해 db에 저장했고,
원래 백엔드에서 비밀번호를 암호화하는 과정을 거쳐 데이터베이스에 저장해야하지만
아직 spring-security 의존성을 주입하지 않아서 내일 진행할 예정이다...
일단 데이터베이스에는 잘 들어간다.
'프로젝트 > ✍️ [전공자들]' 카테고리의 다른 글
[전공자들 11] 로그인 상태관리(useContext) (1) | 2023.12.10 |
---|---|
[전공자들 10] JWT 이해하기(로그인, 토큰발급, 요청보내기) (1) | 2023.12.07 |
[전공자들 8] 7일차 - 로그인 /회원가입/비밀번호 재설정 화면 (1) | 2023.11.30 |
[전공자들 7] 6일차 - 댓글 숨기기, 답글 닉네임 @태그 붙이기 (1) | 2023.11.28 |
[전공자들 6] 개발 5일차 - modal 하나로 가성비를 챙겨보자 (0) | 2023.11.27 |