user.api.js
파일을 새로 생성index.js
에서 useer.api.js
파일을 가져와서 router 객체 내 사용목록에 넣어 줌user.api.js
에서 위와 같이 간략히 router 관련 코드 작성 후 export 해 줌
이후 Postman 으로 정상적으로 응답을 받아오는지 체크
user.js
를 생성user.controller.js
파일을 생성이후 기본세팅을 위와 같이 해 줌
email
, name
, password
를 body에서 뽑아온 다음 이 중에 email 값을 이용해 findOne
메서드로 DB 내에 해당값과 일치하는 데이터가 있는 지 여부를 체크 (가입여부 체크)
만약 값이 존재해서 user 변수에 할당된다면 에러메시지와 함께 에러를 던짐
+a) password 값 암호화 작업
저 코드에서 문제점은 password 값을 백엔드 측에서 바로 알 수 있다는 점임
password 는 민감한 정보이고, 이런 정보는 만약의 상황 (ex. 백엔드 관리자의 실수로 인한 password 누출 등)에 대비해서 해당 데이터 관리자도 그 값을 알 수 없게 암호화 하는 작업이 필요
이 암호화 작업을 원활히 하기 위해 bcrypt 라는 라이브러리를 설치
yarn add bcrypt
controller.js 에서 bcrypt 를 꺼내온 다음 saltRounds 횟수를 설정
bcrypt.genSaltSync
에 saltRounds 값을 인수로 전달하여 salt 값 반환
bcrypt.hashSync
에 요청으로 받아온 password 값과, 반환된 salt 값을 인수로 전달하여 최종적으로 hash 값 반환
salt = 해시 함수에서 암호화된 비밀번호를 생성할 때 추가되는 바이트 단위의 임의의 문자열 값
saltRounds = salt 생성 함수에서 로직을 반복할 횟수로, 값이 커지면 보안에 우수한 암호화 작업이 진행되지만 그만큼 응답이 느려짐
hash = hash된 결과, 단방향 프로세스 작업으로 만들어졌기에 복호화가 어려움
최종적으로 해싱된 값을 User 모델 객체에 값으로 넣어주고 DB 에 저장해 줌
이 과정을 통해 안전하게 보안을 유지하면서 데이터 저장이 가능
user.api.js
에 login 경로에 대한 router 추가userController.js
에 loginUser
라는 함수 선언 및 내부 로직 작성
요청의 body에서 email, password 값을 가져온 다음 email을 이용해 DB에서 일치하는 데이터 정보 1개를 가져옴
이후 bcrypt의 compareSync1
메서드를 이용해서 로그인 요청 데이터의 password 와 저장되어 있는 DB 의 hash 된 password 를 비교하여 반환되는 Boolean 값으로 일치여부 체크
yarn add jsonwebtoken
로그인이 성공할 경우 응답에 토큰을 담아 보내야 함
토큰 발행 기능을 지원하는 jsonwebtoken 라이브러리 설치
.env
에 생성해 줌 (길고 복잡하게 작성할 수록 좋음)위와 같이 만들어 둔 userSchema 에 generateToken
함수를 정의하고 이 함수 안에서 token을 생성하여 return 하도록 정의
jwt 토큰 발행 메서드에 해당 모델 인스턴스에 들어가는 데이터(Document)의 id값을 식별용 정보로 담고, 두 번째 인수에 환경변수로 지정해둔 secretKey를 넣어줌
이렇게 해당 model과 관련된 메서드들을 모델 객체안에 정의해주면 공통성이 생기고 쉽게 재사용이 가능 (클린 코드)
mongoose 에서는 Schema 정의에서 생성되는 인스턴스에 method를 추가할 수 있음
선언된 method 는 후에 특정 데이터를 인수로 넣은 해당 model instance 에서 뽑아 사용 가능
그리고 여기서 정의되는 this 는 해당 인스턴스에 들어있는 Document를 참조하게 됨
최종적으로 위와 같이 코드를 작성
요청 값의 email 정보가 DB에 존재하고, password가 DB의 password 와 일치한다면 200 상태코드와 함께 조회된 user 정보와 토큰을 보내며 함수를 종료
설정한 각 조건에 하나라도 부합되지 않을 경우 바로 아래의 코드가 실행되어 에러를 던지고 400 상태코드와 함께 응답으로 실패를 보냄
이후 Postman 테스트를 통해 토큰값이 응답으로 잘 전달되는 것 확인 가능
문제는 이제 응답으로 오는 값들 중 password
, __v
같은 필요없는 값은 필터링하여 필요한 값만 반환받을 필요가 있음
-
로 지정한 값을 제외하고 응답받을 수도 있으나 이 경우 몇 가지 문제가 있음대신에 Schema 정의 단계에서 toJSON method 를 정의하는 방식을 사용 가능
위와 같이 toJSON 메서드를 Schema 내에 정의하면, mongoDB 에 접근하여 데이터를 반환받을 때 해당 함수는 자동으로 호출되어 json 형식의 값을 반환
반환된 전체 값은 위와 같음
여기서 필요한 field 는 _doc
부분임
메서드 내부에 위와 같이 작성
obj 에 _doc
field 의 모든 문서정보를 담고, 해당 객체 속성들 중 password 를 제거
이후 이 결과를 반환하면 결과적으로 DB에서 값이 반환될 때 password 값은 항상 제외하게 됨
+a) mongoose의 UTC 시간대를 현지 시간대로 조정
{timestamps:true}
를 설정하면 기본적으로 UTC 시간대를 기준으로 Date 를 생성하기 때문에 DB에 저장되어 기록될 때 createdAt, updatedAt 이 UTC 시간대로 저장됨문제 해결을 위해 현지 시간대를 생성하여 반환하는 getLocalTime 함수 작성
Date 객체애 대해 getTimezoneOffset
메서드로 현지 시간과 UTC 시간대의 차이만큼을 분(min) 으로 환산하여 반환 (한국의 경우 -540 반환)
now.getTime
메서드는 Unix Epoch(1970년 1월 1일, 00:00:00 UTC) 이후 현재까지의 시간(ms 단위)을 UTC 시간대로 반환
한국은 UTC보다 9시간 빠르기 때문에 UTC - offset(9시간 차이) 계산으로 현지시간을 구할 수 있음
offset 값이 -540 이기 때문에 UTC에서 -로 빼주는 로직 작성, 결과적으로 UTC에 offset 값을 더하게 되서 실제 한국 현지시간이 결과로 도출됨
해당 getLocalTime
함수를 Schema 에 cereatedAt
, updatedAt
의 type을 Date 로 지정하고 default 에 할당해 줌
이후 해당 Schema를 사용하는 모델 객체가 새로 생성될 때, default에 할당된 함수가 실행되어 그 결과값이 최종적으로 값으로 담기게 됨
결과적으로 내부로직에 따라 함수가 트리거 되는 순간, 현지 시간대 값이 반환됨
회원가입과 마찬가지로 기본적인 세팅은 비슷하게 진행
로그인의 경우 성공했을 때 백엔드에서 그 인증으로 accessToken을 응답으로 넘겨 줌
프론트에서는 이 토큰값을 따로 저장해서 이후 웹 페이지의 특정한 부분에 대한 권한을 관리 가능 (ex. 토큰이 있어야만 접근 가능)
위 코드의 순서대로, 만약 처음 로그인 하는 상황이라면 localStorage에 저장된 토큰값이 없기에 공백으로 저장되고, 이후 요청 시 headers 에 Authorization 키를 추가한 다음 그 값으로 token을 보냄
- 첫 로그인 시 저장된 토큰 값이 없으니 빈 공백값으로 헤더에 추가되어 요청이 넘어감
-Bearer : Token의 유형을 식별하는 문자열로 토큰마다 발행하고 인증하는 방식은 다양함. jwt 토큰의 경우 oauth 2.0 Bearer 인증 방식을 사용한다는 걸 표시하기 위해 토큰값 앞에 Bearer 을 붙임
로그인이 성공했다면 응답값에 토큰이 포함되게 되는데, 이를 localStorage 에 저장. 이후 다음 번 로그인 때는 localStorage.getItem
시 저장된 토큰 값이 있기에 요청 시 헤더에 제대로 된 토큰값을 추가하여 보내게 됨
-현재는 요청 보내는 함수내에서 토큰 값을 체크하나, 이후 상태관리 라이브러리(redux, recoil) 로 accessToken 을 저장하여 axios 로직을 따로 최상위 파일에 두고, 앱 실행마다 요청의 default 값으로 저장해뒀던 전역변수 accessToken을 header 에 담아 보내도록 할 예정