로그인 기능 구현

송현섭 ·2024년 1월 14일
0

백엔드

목록 보기
10/15

회원가입 기능

  • 우선 로그인 기능 자체는 User 와 관련 있는 API 기능들을 만들것이므로 routes 폴더 내에 user.api.js 파일을 새로 생성






  • 이후 routes 폴더 내의 index.js 에서 useer.api.js 파일을 가져와서 router 객체 내 사용목록에 넣어 줌






  • user.api.js 에서 위와 같이 간략히 router 관련 코드 작성 후 export 해 줌

  • 이후 Postman 으로 정상적으로 응답을 받아오는지 체크






  • 이후 model 폴더 안에 새로운 user 관련 Schema를 정의할 user.js 를 생성






  • 각 입력값에 대해 Schema 정의






  • 이후 해당 userSchema 를 이용해 mongoose model 객체를 만든 후 export






  • Controller 도 마찬가지로 새로운 user.controller.js 파일을 생성






  • 이후 기본세팅을 위와 같이 해 줌

  • email, name, password 를 body에서 뽑아온 다음 이 중에 email 값을 이용해 findOne 메서드로 DB 내에 해당값과 일치하는 데이터가 있는 지 여부를 체크 (가입여부 체크)

  • 만약 값이 존재해서 user 변수에 할당된다면 에러메시지와 함께 에러를 던짐






+a) password 값 암호화 작업

  • 저 코드에서 문제점은 password 값을 백엔드 측에서 바로 알 수 있다는 점임

  • password 는 민감한 정보이고, 이런 정보는 만약의 상황 (ex. 백엔드 관리자의 실수로 인한 password 누출 등)에 대비해서 해당 데이터 관리자도 그 값을 알 수 없게 암호화 하는 작업이 필요

  • 이 암호화 작업을 원활히 하기 위해 bcrypt 라는 라이브러리를 설치




yarn add bcrypt
  • bcrypt 설치




  • controller.js 에서 bcrypt 를 꺼내온 다음 saltRounds 횟수를 설정

  • bcrypt.genSaltSync 에 saltRounds 값을 인수로 전달하여 salt 값 반환

  • bcrypt.hashSync 에 요청으로 받아온 password 값과, 반환된 salt 값을 인수로 전달하여 최종적으로 hash 값 반환




salt = 해시 함수에서 암호화된 비밀번호를 생성할 때 추가되는 바이트 단위의 임의의 문자열 값

saltRounds = salt 생성 함수에서 로직을 반복할 횟수로, 값이 커지면 보안에 우수한 암호화 작업이 진행되지만 그만큼 응답이 느려짐

hash = hash된 결과, 단방향 프로세스 작업으로 만들어졌기에 복호화가 어려움




  • 이후 Postman 을 통한 테스트를 통해 해싱된 값이 잘 출력되는 것 확인 가능




  • 최종적으로 해싱된 값을 User 모델 객체에 값으로 넣어주고 DB 에 저장해 줌

  • 이 과정을 통해 안전하게 보안을 유지하면서 데이터 저장이 가능






  • 이후 Postman 테스트를 통해 성공 응답을 받는 것 확인 가능








로그인 기능


  • user.api.js 에 login 경로에 대한 router 추가
    -무언가 생성하는 요청이 아니기에 GET을 써도 될 거 같지만 POST 방식을 써야 함!
    -GET 방식도 body에 데이터를 담아서 보낼 수 있지만, 대부분의 웹 서버에서 GET 요청의 body는 무시하고 이런 형태는 권장되지 않는 방식임






  • userController.jsloginUser 라는 함수 선언 및 내부 로직 작성

  • 요청의 body에서 email, password 값을 가져온 다음 email을 이용해 DB에서 일치하는 데이터 정보 1개를 가져옴

  • 이후 bcrypt의 compareSync1 메서드를 이용해서 로그인 요청 데이터의 password 와 저장되어 있는 DB 의 hash 된 password 를 비교하여 반환되는 Boolean 값으로 일치여부 체크






yarn add jsonwebtoken
  • 로그인이 성공할 경우 응답에 토큰을 담아 보내야 함

  • 토큰 발행 기능을 지원하는 jsonwebtoken 라이브러리 설치






  • jsonwebtoken 라이브러리에 입력할 secret key를 .env 에 생성해 줌 (길고 복잡하게 작성할 수록 좋음)






  • 위와 같이 만들어 둔 userSchema 에 generateToken 함수를 정의하고 이 함수 안에서 token을 생성하여 return 하도록 정의
    jwt 토큰 발행 메서드에 해당 모델 인스턴스에 들어가는 데이터(Document)의 id값을 식별용 정보로 담고, 두 번째 인수에 환경변수로 지정해둔 secretKey를 넣어줌

  • 이렇게 해당 model과 관련된 메서드들을 모델 객체안에 정의해주면 공통성이 생기고 쉽게 재사용이 가능 (클린 코드)



  • mongoose 에서는 Schema 정의에서 생성되는 인스턴스에 method를 추가할 수 있음

  • 선언된 method 는 후에 특정 데이터를 인수로 넣은 해당 model instance 에서 뽑아 사용 가능

  • 그리고 여기서 정의되는 this 는 해당 인스턴스에 들어있는 Document를 참조하게 됨







  • 이후 bcrypt로 password 비교결과가 일치할 경우 생성한 user 인스턴스에서 정의되어 있는 generateToken 함수를 실행하여 token 발행







  • 최종적으로 위와 같이 코드를 작성

  • 요청 값의 email 정보가 DB에 존재하고, password가 DB의 password 와 일치한다면 200 상태코드와 함께 조회된 user 정보와 토큰을 보내며 함수를 종료

  • 설정한 각 조건에 하나라도 부합되지 않을 경우 바로 아래의 코드가 실행되어 에러를 던지고 400 상태코드와 함께 응답으로 실패를 보냄






  • 이후 Postman 테스트를 통해 토큰값이 응답으로 잘 전달되는 것 확인 가능

  • 문제는 이제 응답으로 오는 값들 중 password, __v 같은 필요없는 값은 필터링하여 필요한 값만 반환받을 필요가 있음






  • 위와 같이 DB에서 값을 꺼내오는 로직 자체에서 - 로 지정한 값을 제외하고 응답받을 수도 있으나 이 경우 몇 가지 문제가 있음

    문제점 1 = 이 경우 해당 함수 내 findOne 에서만 일시적으로 password 같은 지정한 값을 제외하고 받아오는 것이기에 find 메서드로 DB에서 값을 받아오는 모든 로직에 하나하나 제외값을 지정해줘야 함

    문제점 2 = 여기서는 DB에서 받아온 정보를 user 변수에 담는데, hash 된 비밀번호를 DB에 저장된 user.password 와 비교하는 로직이 있음, 즉 password 값을 사용하기 때문에 제외가 불가능






  • 대신에 Schema 정의 단계에서 toJSON method 를 정의하는 방식을 사용 가능

  • 위와 같이 toJSON 메서드를 Schema 내에 정의하면, mongoDB 에 접근하여 데이터를 반환받을 때 해당 함수는 자동으로 호출되어 json 형식의 값을 반환






  • 반환된 전체 값은 위와 같음

  • 여기서 필요한 field 는 _doc 부분임






  • 메서드 내부에 위와 같이 작성

  • obj 에 _doc field 의 모든 문서정보를 담고, 해당 객체 속성들 중 password 를 제거

  • 이후 이 결과를 반환하면 결과적으로 DB에서 값이 반환될 때 password 값은 항상 제외하게 됨




+a) mongoose의 UTC 시간대를 현지 시간대로 조정

  • mongoose의 {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에 할당된 함수가 실행되어 그 결과값이 최종적으로 값으로 담기게 됨
    결과적으로 내부로직에 따라 함수가 트리거 되는 순간, 현지 시간대 값이 반환됨







프론트와 회원가입 기능 연동


  • 간단하게 기본 UI 구조를 잡아준 다음, 각각의 Input 입력값을 setState 함수로 상태저장






  • 이후 input 태그들을 form 태그로 묶어서 onsubmit 속성에 handlesubmit 함수를 할당해주고 해당 함수 내부 로직 작성

    -여기서 onsubmit 지정한 함수 트리거 시 form 태그 특성 상 다시 화면을 렌더링 하는데 이는 불필요한 작업임으로 e.preventDefault() 메서드로 리렌더 발생을 방지

    -현재 백엔드 서버 배포된 곳에 방금 작성한 코드는 적용되어 있지 않기에 기존의 env 환경변수로 설정한 주소를 그대로 axios 요청으로 쓰면 404에러가 발생하기에 대신에 로컬로 서버를 열고 로컬주소로 백엔드 테스트를 진행






  • 이후 프론트에서의 동작으로 백엔드에 요청 처리가 정상적으로 이루어지는 것 확인가능








프론트 로그인 기능 연동 (+ 토큰 저장)


  • 회원가입과 마찬가지로 기본적인 세팅은 비슷하게 진행

  • 로그인의 경우 성공했을 때 백엔드에서 그 인증으로 accessToken을 응답으로 넘겨 줌

  • 프론트에서는 이 토큰값을 따로 저장해서 이후 웹 페이지의 특정한 부분에 대한 권한을 관리 가능 (ex. 토큰이 있어야만 접근 가능)

  • 위 코드의 순서대로, 만약 처음 로그인 하는 상황이라면 localStorage에 저장된 토큰값이 없기에 공백으로 저장되고, 이후 요청 시 headers 에 Authorization 키를 추가한 다음 그 값으로 token을 보냄
    - 첫 로그인 시 저장된 토큰 값이 없으니 빈 공백값으로 헤더에 추가되어 요청이 넘어감

    -Bearer : Token의 유형을 식별하는 문자열로 토큰마다 발행하고 인증하는 방식은 다양함. jwt 토큰의 경우 oauth 2.0 Bearer 인증 방식을 사용한다는 걸 표시하기 위해 토큰값 앞에 Bearer 을 붙임

  • 로그인이 성공했다면 응답값에 토큰이 포함되게 되는데, 이를 localStorage 에 저장. 이후 다음 번 로그인 때는 localStorage.getItem 시 저장된 토큰 값이 있기에 요청 시 헤더에 제대로 된 토큰값을 추가하여 보내게 됨

    -현재는 요청 보내는 함수내에서 토큰 값을 체크하나, 이후 상태관리 라이브러리(redux, recoil) 로 accessToken 을 저장하여 axios 로직을 따로 최상위 파일에 두고, 앱 실행마다 요청의 default 값으로 저장해뒀던 전역변수 accessToken을 header 에 담아 보내도록 할 예정

profile
막 발걸음을 뗀 신입

0개의 댓글

관련 채용 정보