출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1/dashboard
리팩토링 하기전에 테스트 코드를 먼저 작성하자.
- 그래야 코드를 변경한 이후에 불안하지 않다.
- 변경한 코드가 무언가를 깨트리지 않았다는 것을 확인할 수 있다.
테스트 할 것
- 폼에 이상한 값이 들어간 경우에 다시 폼이 보여지는가?
- 폼에 값이 정상적인 경우
- 가입한 회원데이터나 존재하는가?
- 이메일이 보내지는가?
리팩토링
- 메소드가 너무 길지 않은가?
- 코드를 읽기 쉬운가?
- 내가 작성한 코드를 내가 읽기 어렵다면 남들에겐 훨씬 더 어렵다.
- 코드가 적절한 위치에 있는가?
- 객체들 사이의 의존관계
- 책임이 너무 많지 않은지
실습
테스트 부터
코드를 변경한 이후에 변경한 코드가 아무것도 깨트리지 않음을 증명
이상 입력값 테스트 - 폼에 이상한 값이 들어간 경우에 다시 폼이 보여지는가?
@DisplayName("회원 가입 처리 - 입력값 오류")
@Test
void singUpSubmit_wrong_input() throws Exception {
mockMvc.perform(post("/sign-up")
.param("nickname", "jungi")
.param("email", "email..")
.param("password", "12345"))
.andExpect(status().isOk())
.andExpect(view().name("account/sign-up"));
}
mockMvc.perform으로 post방식으로 sign-up에 파라미터들을 담아서보내는데
기댓값이 200 그리고 보여지는 뷰가 다시 가입 페이지로 보여지는 것
그리고 이런 에러가 나오게 되었다.
상태 200을 기대했지만 403이 나왔다.
403에러는 권한이 부족하여 나오는 에러이다.
이유는 CSRF(Cross-site request forgery)설정이 켜져있어서 이다.
타 사이트에서 공격하는 사이트 대상으로 form-data를 보내는 것
은행 계좌 이체 데이터를 타 사이트에서 은행 사이트에 보내서 유출을 시도하는것
이를 방지하기 위해 CSRF토큰을 사용한다.
ThymeLeaf Template - Spring security - Spring Mvc
이 세 가지가 조합되어서 CSRF 기능을 지원해준다.
csrf 토큰 값이 같이 서버 쪽으로 전송이 된다.
이를 이용해서 내가 만들어준 폼에서 온 데이터임을 검증하고 사용한다.
csrf토큰 없이 폼 만 오거나 csrf 토큰값이 다르게 온다면 권한이 없기 때문에 403에러가 나오게 된다.
@DisplayName("회원 가입 처리 - 입력값 오류")
@Test
void singUpSubmit_wrong_input() throws Exception {
mockMvc.perform(post("/sign-up")
.param("nickname", "jungi")
.param("email", "email..")
.param("password", "12345")
.with(csrf())) <-추가
.andExpect(status().isOk())
.andExpect(view().name("account/sign-up"));
}
폼 데이터 전송하는 테스트에서는 .with(csrf())를 추가해주자.
// public static SecurityMockMvcRequestPostProcessors.CsrfRequestPostProcessor csrf()
csrf토큰을 포함해주어서 검사해야 안정적인 검사가 가능하다.
입력값 정상 테스트 - 가입한 회원데이터나 존재하는가?
@DisplayName("회원 가입 처리 - 입력값 정상")
@Test
void singUpSubmit_correct_input() throws Exception {
mockMvc.perform(post("/sign-up")
.param("nickname", "jungi")
.param("email", "jungi@email.com")
.param("password", "12345678")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/"));
assertTrue(accountRepository.existsByEmail("jungi@email.com"));
}
입력값 정상 테스트 - 메일이 보내지는가?
@DisplayName("회원 가입 처리 - 입력값 정상")
@Test
void singUpSubmit_correct_input() throws Exception {
mockMvc.perform(post("/sign-up")
.param("nickname", "jungi")
.param("email", "jungi@email.com")
.param("password", "12345678")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/"));
assertTrue(accountRepository.existsByEmail("jungi@email.com"));
then(javaMailSender).should().send(any(SimpleMailMessage.class));
}
그리고 나서 javaMailSender에서 어떤 SimpleMailMessage의 클래스 타입의 클래스라도 무조건 보내라.
then - BDDMokito
메일을 보내지 않았을 경우 테스트 했을 때 나오는 에러
메일을 보낼때 인터페이스만 관리하고 실제로 보내는 것은 외부 서비스
메일은 smtp와 연결을 해서 gmail로 이메일 발송할 예정
이 과정을 테스트로 작성하기엔 당장엔 너무 많기 떄문에 외부 서비스로 맛만 보는것
리팩토링
@PostMapping("/sign-up")
public String signUpSubmit(@Valid SignUpForm signUpForm, Errors errors) {
if(errors.hasErrors()){
return "account/sign-up";
}
Account account = Account.builder()
.email(signUpForm.getEmail())
.nickname(signUpForm.getNickname())
.password(signUpForm.getPassword())
.studyCreateByWeb(true)
.studyEnrollmentResultByWeb(true)
.studyUpdateByWeb(true)
.build();
Account newAccount = accountRepository.save(account);
newAccount.generateEmailCheckToken(); //UUID
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(newAccount.getEmail());
mailMessage.setSubject("스터디올래, 회원 가입 인증");
mailMessage.setText("/check-email-token?token=" +
newAccount.getEmailCheckToken() + "&email=" + newAccount.getEmail());
javaMailSender.send(mailMessage);
return "redirect:/";
}
-> 메서드로 기능 별 분리
@PostMapping("/sign-up")
public String signUpSubmit(@Valid SignUpForm signUpForm, Errors errors) {
if(errors.hasErrors()){
return "account/sign-up";
}
Account newAccount = saveNewAccount(signUpForm);
newAccount.generateEmailCheckToken(); //UUID
sendSignUpConfirmEmail(newAccount);
return "redirect:/";
}
private Account saveNewAccount(@Valid SignUpForm signUpForm) {
Account account = Account.builder()
.email(signUpForm.getEmail())
.nickname(signUpForm.getNickname())
.password(signUpForm.getPassword())
.studyCreateByWeb(true)
.studyEnrollmentResultByWeb(true)
.studyUpdateByWeb(true)
.build();
return accountRepository.save(account);
}
private void sendSignUpConfirmEmail(Account newAccount) {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(newAccount.getEmail());
mailMessage.setSubject("스터디올래, 회원 가입 인증");
mailMessage.setText("/check-email-token?token=" +
newAccount.getEmailCheckToken() + "&email=" + newAccount.getEmail());
javaMailSender.send(mailMessage);
}
-> Controller에 맞지 않는 책임 이나 과한 역할
-> 역할 분리 AccountService로 이전
package com.studyolle.account;
@Controller
@RequiredArgsConstructor
public class AccountController {
private final SignUpFormValidator signUpFormValidator;
private final AccountService accountService;
@InitBinder("signUpForm")
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.addValidators(signUpFormValidator);
}
@GetMapping("/sign-up")
public String signUpForm(Model model) {
model.addAttribute(new SignUpForm());
return "account/sign-up";
}
@PostMapping("/sign-up")
public String signUpSubmit(@Valid SignUpForm signUpForm, Errors errors) {
if(errors.hasErrors()){
return "account/sign-up";
}
accountService.processNewAccount(signUpForm);
return "redirect:/";
}
}
package com.studyolle.account;
import com.studyolle.domain.Account;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import javax.validation.Valid;
@Service
@RequiredArgsConstructor
public class AccountService {
private final AccountRepository accountRepository;
private final JavaMailSender javaMailSender;
public void processNewAccount(SignUpForm signUpForm) {
Account newAccount = saveNewAccount(signUpForm);
newAccount.generateEmailCheckToken(); //UUID
sendSignUpConfirmEmail(newAccount);
}
private Account saveNewAccount(@Valid SignUpForm signUpForm) {
Account account = Account.builder()
.email(signUpForm.getEmail())
.nickname(signUpForm.getNickname())
.password(signUpForm.getPassword()) // TODO encoding 해야 함 (hash로 바꿔서)
.studyCreateByWeb(true)
.studyEnrollmentResultByWeb(true)
.studyUpdateByWeb(true)
.build();
return accountRepository.save(account);
}
private void sendSignUpConfirmEmail(Account newAccount) {
SimpleMailMessage mailMessage = new SimpleMailMessage();
//Subject -> 제목 설정 | setText -> 본문설정
mailMessage.setTo(newAccount.getEmail());
mailMessage.setSubject("스터디올래, 회원 가입 인증");
mailMessage.setText("/check-email-token?token=" +
newAccount.getEmailCheckToken() + "&email=" + newAccount.getEmail());
javaMailSender.send(mailMessage);
}
}
'프로젝트 정리 > 스프링과 JPA 기반 웹 애플리케이션 개발' 카테고리의 다른 글
10. 회원 가입: 인증 메일 확인 (0) | 2021.02.24 |
---|---|
9. 회원 가입: 패스워드 인코더 (0) | 2021.02.24 |
6 - 2 . 회원 가입 폼 서브밋 처리 (0) | 2021.02.17 |
6 - 1 . 회원 가입 : 폼 서브밋 검증 (0) | 2021.02.16 |
5. 회원가입 뷰 (0) | 2021.02.16 |