뼈대에 대한 설명은 주석으로 대체하고, getMembers()와 getMembersByNickName메서드에 대해서만 알아보자.
단순히 Member테이블에 있는 모든 member를 리스트 형태로 가져오는 명령이다.
① members.stream()
② .map(member -> new GetMemberRes(member.getId(), member.getNickName(), member.getEmail(), member.getPassword()))
③ collect(Collectors.toList())
닉네임은 멤버 간의 중복을 허용할 수도 있고 불허할 수도 있다. 만약 닉네임 중복이 없는 경우라면 굳이 List로 반환할 필요는 없을 것이다.
다만, 컨트롤러에서 RequestParam으로 닉네임을 입력 받은 경우에는 해당 닉네임의 멤버를 반환하고, RequestParam을 입력 받지 않은 경우에는 모든 멤버의 리스트를 출력해야 하므로, 두 경우에 대해 반환타입을 맞춰주기 위해 List로 선언한 것이다. 당연히 닉네임 중복이 없다하더라도 List로 반환하는 것 자체가 문제가 되지는 않는다.
이 로직은 클라이언트로부터 email과 nickName을 입력받아 클라이언트의 이메일을 수정한다. 먼저 입력받은 이메일과 일치하는 멤버를 찾아주자. 그 후 jwtService의 getUserIdx() 메서드로 user의 인덱스를 가져오자.
① HttpServletRequest request
② request.getHeader("Authorization")
따라서, 이 코드는 현재 HTTP 요청에서 "Authorization" 헤더 값을 추출하여 JWT를 반환하는 기능을 수행한다.
만약 이 jwt 값이 null이라면, jwt가 http 요청의 헤더에 입력되지 않은 것이므로 예외를 호출한다.
JWT를 성공적으로 추출했다면, JWT를 검증하고 parsing 해야 한다.
※ 파싱(Parsing)
주어진 데이터를 해석하고 구조화하는 과정을 말한다. 데이터를 파싱한다는 것은 원시 데이터를 의미있는 부분으로 분해하고, 그 부분들을 의미 있는 방식으로 해석하는 것을 의미한다.
① Jws<Claims> claims
② try문
③ claims.getBody().get()
④ return
한마디로 JWT를 파싱하여 검증하고, JWT의 본문(Claims)에서 memberId를 추출하여 사용자의 고유 식별자를 반환하는 코드인 것이다. 이메일로 데이터베이스에서 조회한 멤버의 Id 값과 현재 요청 헤더에 있는 jwt에서 추출한 Id 값이 일치하는지 확인 후 결과에 따라 예외를 호출하거나 유저 네임 변경을 시도할 것이다.
JWT를 확인하는 로직은 매우 중요하고 자주 쓰이기 때문에 반드시 이해하고 넘어가야 한다. 이해가 잘 안된 사람들을 위해 아래의 예시를 적어놓겠다. 이해가 된 사람들은 다음으로 넘어가도 된다.
chrome이라는 사람이 있고, kin이라는 사람이 있다고 하자. 그리고 kin이 chrome의 이메일을 알고 있다고 하자. 이 때, kin이라는 사람이 chrome의 이메일을 가지고 chrome의 닉네임을 hyunseop으로 마음대로 변경하려고 한다. 이 요청이 처리될 수 있을까?
당연히 처리되지 않는다. 그 이유가 뭘까? kin이라는 사람이 patch 요청을 서버로 보내게 되면, 서버에서는 kin이 보낸 HTTP 요청 헤더의 jwt 값에서 kin의 id를 추출할 것이다. 그리고 DB에서 입력받은 이메일에 해당하는 멤버를 찾아 id를 가져와 두 값을 비교한다.
요청 헤더에서 추출된 id의 값은 kin의 것이고, DB에서 조회된 id의 값은 chrome의 것이기 때문에 요청이 처리되지 않는 것이다. 즉, 본인만이 본인의 닉네임을 수정할 수 있게 되는 것이다.
JWT를 확인하는 코드를 추가해주지 않을 경우, 위와 같이 kin이 chrome의 닉네임을 마음대로 바꾸는 것이 가능해진다.
① @Transactional
② getReferenceById()
updateNickName메서드는 Member 클래스에 정의되어 있다.
왜 MemberService에 정의하지 않았는지 궁금해할 수도 있을 것이다. 만약 MemberService에 별도로 updateNickName을 정의했다면, 그 메서드 내부에서 다시 해당 멤버를 찾거나, 멤버를 넣어주어야 하는데, 이러한 불편한 점을 개선한 것으로 볼 수 있다. 물론, 이것은 개발자의 선택이니, 필수 사항은 아니다. 다만 코드의 가독성을 높이기 위한 방법일 뿐이다.
일련의 과정이 마치고 나면 회원정보가 수정되었다는 결과와 함께 데이터베이스에서 변경된 닉네임을 확인할 수 있게 될 것이다.
이 API는 클라이언트에게 email과 password를 받아서 최종적으로 멤버를 delete하는 요청이다.
쿼리스트링으로 전달된 이메일과 패스워드를 DTO의 생성자 입력으로 넣어주자.
① findMemberByEmail() 메서드
② List<Board> boards
삭제하려는 멤버가 작성한 게시글이 없을 때에만, 멤버를 삭제하는 쿼리가 동작한다.
사실은 멤버삭제 또한 닉네임 변경 API처럼, 멤버의 JWT 토큰을 헤더의 Authorization 필드에 입력했을 때에만 동작하게 만들어야 한다. 이것은 비단 삭제 뿐 아니라, 로그인이 필요한 모든 요청에 대해 헤더의 Authorization 필드의 JWT 값을 확인해야 한다.
다만, 이번 포스팅에서는 중복된 코드를 계속 적을 필요는 없다보니 생략한 것뿐이다. 실제로는 JWT를 확인하는 로직이 매번 추가되어야 한다.