[SptringBoot] fetch api를 post 방식으로 보내기

huihui·2021년 2월 2일
0

로그인을 할 때 아이디, 비밀번호를 입력하고 로그인 버튼을 누른다.
서버에서는 입력된 아이디, 비밀번호를 데이터베이스와 비교하여 일치 여부를 판단한다.

이번 포스팅에서는 아이디, 비밀번호가 불일치일 때 페이지 전환 없이 ajax를 활용하여 처리해보고자 한다.

fetch api에서 post 방식을 사용할 것이다.

window.addEventListener('load', (e) => {
    const loginBtn = document.querySelector('.login-btn');
    const form = document.querySelector('.form');

    loginBtn.addEventListener('click', (e) => {
        e.preventDefault(); // 기본 폼 동작 막기

        let loginId = document.querySelector('.login-id').value;
        let password = document.querySelector('.password').value;
        
        let loginData = {
            method: 'POST',
            body: JSON.stringify({ loginId, password }),
            headers: {
                'Content-Type': 'application/json'
            }
        };

        fetch(`/auth/login/validate`, loginData)
            .then(response => response.json())
            .then(response => {
                if (response) 
                    form.submit(); // 기본 폼 동작 풀기
                
            })
            .catch(err => {
                alert('아이디 또는 패스워드를 확인해 주세요.');
            });

    });
    
});

fetch 방식으로 post 전송을 할 때는 반드시 지정해야할 3가지가 있다.
method, body, headers이다.
body는 보낼 데이터들을 객체로 묶어 보내야 한다.

서버에서는 클라이언트가 데이터를 JSON 형식으로 보내게 되면 받아들일 수 없기 때문에 JSON을 String 형식으로 변환시켜 보내야 한다.


다음은 서버 코드이다.
서버에서는 클라이언트로부터 전달받은 body 객체가 String 형식이기 때문에 이를 활용하기 위해서는 다시 JSON 형식으로 변환시켜야 한다.

변환을 하는 방법은 여러가지가 있지만 구글에서 제공해주는 GSON을 사용해보겠다.

GSON을 pom.xml에 dependency로 추가를 한다.

현재 스프링부트를 사용하고 있기 때문에 정확한 버전을 설정하지 않아도 자동으로 알맞은 버전을 선택해준다.

@RequestMapping("/auth/")
public class HomeController {

  @Autowired
  MemberService memberService;

  @PostMapping("login/validate")
  @ResponseBody
  public String loginValidate(@RequestBody String data) {

      JsonParser parser = new JsonParser();
      JsonElement element = parser.parse(data);
      String loginId = element.getAsJsonObject().get("loginId").getAsString();
      String password = element.getAsJsonObject().get("password").getAsString();

      BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
      Member member = memberService.getMemberByLoginId(loginId);

      if (member == null)
          return null;

      if (!passwordEncoder.matches(password,member.getPassword()))
          return null;
      else
          return "true";
  }
}

클라이언트에서 전송한 데이터를 매개변수로 받아 활용하기 위해서는 @RequestBody 어노테이션을 사용한다.
@RequestParam과의 차이는 @RequestParam의 경우는 클라이언트에서 보낸 데이터의 변수명과 매개변수의 명이 정확하게 일치해야만 사용할 수 있지만, @RequestBody는 변수명이 일치하지 않아도 된다.
또한 @RequestParam은 객체를 받을 수 없는 반면에 @RequestBody는 전달된 객체를 받을 수 있다.

현재 매개변수 data는 클라이언트가 보낸 JSON 객체를 String 형식으로 담고 있기 때문에 이를 GSON을 통해 변환하기 위해서는 아래와 같은 코드를 사용하였다.

JsonParser parser = new JsonParser();
JsonElement element = parser.parse(data);
String loginId = element.getAsJsonObject().get("loginId").getAsString();
String password = element.getAsJsonObject().get("password").getAsString();

이로써 사용자가 입력한 loginId와 password를 서버측에서 받았으니 이를 db와 일치여부를 검사해야 한다.

memberService에서 loginId에 일치하는 Member 객체 하나를 꺼내와 대조해볼 필요가 있다.
현재 db에 저장된 password는 BCrypt 암호화가 되어 있어서 꺼내온 Member 객체의 패스워드를 비교하려면 matches 메서드를 이용해야 한다.
이 부분이 바로 다음 코드이다.

BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
Member member = memberService.getMemberByLoginId(loginId);

if (member == null)
    return null;

if (!passwordEncoder.matches(password,member.getPassword()))
    return null;
else
    return "true";

아이디나 패스워드가 일치하지 않는 경우에 null을 반환하는 이유는 다음과 같다.
현재 서버 측에서 리턴하는 값이 fetch문 then절의 매개변수로 넘어가는 로직이기 때문에 null 값이 리턴되는 경우에 에러가 난 것으로 간주해 catch문으로 빠지게 된다.
그래서 클라이언트 코드에서 catch문에 '아이디 또는 패스워드를 확인해 주세요.'라는 alert를 띄우면 된다.

만약 아이디, 비밀번호가 일치하여 "true" String을 리턴한다면 then절에서 이를 받아 json 형식으로 변환하면 String -> boolean 자료형으로 바뀌게 된다.


여기서 주의할 점은 다음과 같다.
로그인 버튼을 누른 순간에 html의 form에 지정된 action url로 데이터가 전송이 된다.
그리고 fetch문의 url로도 데이터가 전송이 된다.

그럼 버튼을 한 번만 눌렀는데 총 두 개의 url로 사용자의 아이디, 비밀번호가 전송되는 것인가?
이런 경우에는 fetch의 비동기 방식으로 인해 순서가 꼬일 수 있다.

이를 방지하기 위해 e.preventDefault()를 통해 폼의 기본적인 전송 기능을 제한했다.
이후 fetch문을 통해 서버와 데이터를 주고 받고 아이디와 비밀번호가 일치하는 경우에만 form.submit()을 통해 폼의 기본적인 전송 기능을 다시 사용할 수 있도록 하여 순서의 꼬임을 방지하였다.

0개의 댓글