오늘은 정말 중요한 내용 중 하나인 로그인 파트에 대해서 배웠어요!
간략히 요약하면, Backend는 어떤 사람이 api를 요청하는지 알 수 없기 때문에 유저의 모든 api 요청에 accessToken을 함께 보내줬습니다! http request header의 authorization에 Bearer 방식으로 accessToken을 첨부해서 보냈죠?
이 토큰을 받은 Backend는 accessToken을 열어서 그 안에있는 객체(JSON)의 내용물을 보고 누가 요청을 한건지 구분 할 수 있던 것이였습니다!
이러한 작업이 가능한 이유는 accessToken이 JWT(Json-Web-Token)이기 때문이었습니다.
이를 이해하기 위해 암호화(Encoded)와 복호화(Decoded)에 대해 알아봤죠? JWT는 유저의 이름, id, 만료시간 등 을 객체로 만들고 이를 암호화/토큰화 시켜 브라우저에 전달하는 방식이였습니다!
이 방식으로 Browser에서 요청이 들어오면 더 이상 DB에서 토큰을 찾지 않고, Backend에서 바로 복호화를 통해 로그인 처리를 할 수 있게 된 것이였습니다!
JWT에 단점이 있었어요! 누구든 이 파일을 읽을 수 있다는 점 이였습니다. 그렇다면 JWT를 탈취해 내용을 읽고 변경하게 된다면, 보안상 문제가 커지겠죠! 이를 방지하기위해 서명처리(비밀번호 처리)한다고 했습니다. JWT발급 시 함께 나오는 password가 있는데, JWT 내부의 내용을 누구나 읽을 수 있지만 이 비밀번호가 없다면 조작이 불가능했습니다!
따라서, JWT 내부에는 중요한 개인정보 등을 담으면 안된다는 중요한 포인트가 있었습니다!
암호화 2가지 방법도 알아봤습니다. 오늘 앞선 과정에서 암호화와 복호화에 대해 알아봤죠!
암호화와 복호화 둘 다 가능한 것을 양방향 암호화, 암호화는 가능한데 복호화가 되지않는 단방향 암호화가 있다고 했습니다!
비밀번호 같은 정보는 단방향 암호화를 사용해 복호화 시킬 수 없게 한다고 했죠!
복호화가 되지 않는다는 말은 암호화된 정보가 탈취되어도 그 정보가 어떤 정보인지 알 수 없다는 말이겠죠! 이런걸 Hashing이라 합니다!
이 모든 절차를 통해 API요청 시 토큰을 함께 보내 인증(Authentication) 과 인가(Authorization)를 받아야 했습니다.
그러기 위해 로그인을 하여 받게되는 accessToken을 HTTP header에 Authorization(인가)에 Bearer 방식으로 담아 넘겨주었습니다! 여기서 Bearer는 토큰 관련 정보가 담겨있다는 걸 알려주기 위한 이름일 뿐이라 했고 관례상 붙여주는 것 이였습니다!
이 부분을 우리의 프로젝트에도 적용해봤죠!
그렇게 하면서 ApolloSetting 컴포넌트를 만들어서 Apollo 관련 소스코드는 따로 관리해주었습니다! 그럼 app.tsx를 더 깔끔하게 관리할 수 있겠죠? 이렇게 컴포넌트 형식으로 관리해주시는 것이 리액트의 핵심입니다!
다시 돌아가서 app.tsx에서 recoil을 사용해 accessToken과 setAccessToken을 넘겨 주면 되겠습니다. 이 과정에서 넘겨줄 내용들을 atom을 사용해서 담아 묶어 전달하였습니다!
로그인 화면에서 로그인을 하여 받게되는 accessToken을 recoil을 통해 _app.tsx에 state로 저장하였고,
uploadLink내 headers에 accessToken을 추가해 주었습니다. 이로써 모든 컴포넌트에서 gql-api요청 시, 로그인 관련 정보를 함께 보내줄 수 있도록 설정해주었습니다!
오늘 한 내용을 모두 외우려 하지 마세요, 흐름을 이해하시고 기능을 구현하실 수만 있으면 됩니다!
- 굿모닝 알고리즘 문제
오늘 문제 읽자마자 왜저래 진짜..하고 덮고 기본문제 푸는중 ㅠㅠㅠㅠ
어찌해야할지 생각이라도 해보자 ?!수포자는 수학을 포기한 사람의 준말입니다. 수포자 삼인방은 모의고사에 수학 문제를 전부 찍으려 합니다. 수포자는 1번 문제부터 마지막 문제까지 다음과 같이 찍습니다. 1번 수포자가 찍는 방식: 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ... 2번 수포자가 찍는 방식: 2, 1, 2, 3, 2, 4, 2, 5, 2, 1, 2, 3, 2, 4, 2, 5, ... 3번 수포자가 찍는 방식: 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, ... 1번 문제부터 마지막 문제까지의 정답이 순서대로 들은 배열 answers가 주어졌을 때, 가장 많은 문제를 맞힌 사람이 누구인지 배열에 담아 return 하도록 solution 함수를 작성해주세요. 제한 조건 시험은 최대 10,000 문제로 구성되어있습니다. 문제의 정답은 1, 2, 3, 4, 5중 하나입니다. 가장 높은 점수를 받은 사람이 여럿일 경우, return하는 값을 오름차순 정렬해주세요. 입출력 예 answers return [1,2,3,4,5] [1] [1,3,2,4,2] [1,2,3]
- 지난시간 수업 끝날때쯤 fetchpolicy에 대해 나온 질문 꼼꼼히 리뷰해주시는 갓원두 멘토쌤 !
import { useQuery, gql } from "@apollo/client"; import { useEffect, useState } from "react"; import FetchPolicyTest from "../../src/components/units/board/21-fetch-policy"; const FETCH_BOARDS = gql` query fetchBoards { fetchBoards { _id writer title } } `; export default function GlobalStatePage() { const { data } = useQuery(FETCH_BOARDS); const [aaa, setAaa] = useState(false); const onClickAaa = () => { setAaa(true); }; return ( <div> <button onClick={onClickAaa}> 클릭하면 새로운 컴포넌트가 나타납니다!! </button> {aaa && <FetchPolicyTest />} </div> ); }```
이 fetch하는 쿼리문을 봤을때, api요청이 어디서 일어나는가에 중점 !
(cache에서 찾아오는건지, api가서 요청 데이터 받아오는건지 !)
밑에 컴포넌트 안에서 동일한 api를 요청하는 쿼리문인데, 기존에 있던 cache에서 받아오기 때문에
네트워크를 봐보면 graphql요청이 일어나지 않는다. 다만 gql에 받고싶은 데이터를 더 요청하거나
추가되는 데이터가 있으면 api에 요청을 해서 받아오게 되는거고!
이 fetchpolicy의 방식 (데이터 가져오는 방식)은 usequery에서 지정해줄수 있다
- 로그인 프로세스 이해
- 로그인을 이해하려면 역사를 알아야돼 !
-쿠키, 세션, 토큰 3가지 방법이 있는데 쿠키와 세션은 보안이 취약!
-예전에는 이메일과 패스워드를 받아서 백엔드에 session이라고 컴퓨터 메모리에 저장했었다!
-그래서 그 이메일과 패스워드로 구분가능한 session Id를 다시 브라우저에 넘겨주고,
payment나 createProduct는 회원만 할 수 있는거여서, 그때 이 sessionId를 이용하여
유저정보를 받아 기능들을 이용할 수 있게했다 !
-그 정보들은 메모리에 저장을 했었다!
-그래서 회원이 많아질수록, 저장할 메모리가 넘 많이 필요했어! (scale up)
-백엔드서버컴퓨터를 하나 더 만들고, 같은 api를 쓰니까 아무데나 요청이 오면 응답하는 식으로
요청을 분산했다! 컴퓨터가 부족하면 하나 둘 똑같은 컴퓨터만 늘려가면 되니까 ! (트래픽 부하 분산) (scale out)
-근데 이러니까 메인컴퓨터 말고 다른데서 로그인이 안되었다...! 다른 컴퓨터 메모리session에는 정보가 없어서...! (scale out의 문제점 )
-메모리 세션을 가지고 있는 메인컴퓨터를 statefull하다라고 한다 / statefull해서 scale out하기가 어려워요!
-상태가 없는 (session메모리가 없는) 컴퓨터를 stateless상태라고 한다
- 우리는 stateless상태에서 로그인을 편하게 하는 방법을 찾자 !
- 백엔드 컴퓨터가 많은 상태에서 어디에다가 로그인 정보를 저장할것인가?
-아까 그 sessioinId를 이제 백엔드 뒤쪽에서 토큰id라고 해서 저장해놓고 가져다 쓰는 방식으로 진행할거야 !
-api요청할때마다 state로 받아온 토큰을 넘겨서, 로그인되었구나를 알려주고 검증해서 다양한 기능들을 사용하도록!
-근데 이러면 결국엔 db로 부하가 몰리니까 완벽한 해결책은 아냐 !- 그러면 저 db에 있는 테이블을 나누자!
- 테이블을 파티셔닝해서 나누자 ! (한대한대 db컴퓨터를 나눠서 저장한다)
- 데이터베이스가 나누어져서 디스크라는 영구저장공간에 들어가있는데! 여기는 찾는것도 저장하는것도 느리다
-createproduct하나 하는데 왔다갔다 디스크를 긁으면 성능에 문제가 생기지 !
-그러면 디스크 말고, 메모리기반 데이터베이스에 저장하면 되지 않을까? (그중 대표는 redis)
현재 가장 많이 쓰이는 로그인 방식 2가지^중 하나가 메모리기반 데이터베이스를 활용한 이^ 방식 !
두번째는 메모리에도 저장안하고, Redis도 필요없꼬! 객체를 하나 만들어놓고, 안에 id를 뺀 나머지 데이터를 암호화해서 저장한다! (암호화하면 알수없는 문자열형태로 바뀌게 됨) 그래서 그 암호화 했던 내용을 토큰아이디로 쓰자! 가 되어버림 (토큰아이디를 복호화하면 값이 나올테니까!)(그러면 백엔드 안가도 데이터가 나오니까!)- 저 토큰 이름을 json(javascript object natation web token 이라고 부른다!)jwt
- JWT토큰? 이건 또 뭐야!
- 저 토큰을 복호화 하면, 로그인 데이터가 담긴 객체가 나온다
-로그인에서는 최초한번 맞는지 확인 후 , 토큰(암호화된정보,state)를 생성해준다
-createproduct에 같이 그 정보를 들고가서, create할때 원래 db를 긁거나 redis같은데 정보를 가지러 들를필요가 없다! 복호화해서 데이터를 바꿔주면 됨 !
jwt.io들어가보면, 암호화, 복호화 작업된게 나온다 payload부분이 우리가 저장한 데이터 부분이다!
만료시간 1시간!(로그인이 1시간 뒤면 꺼짐) 짜리 토큰을 내 포폴 페이지에서 가입해서 mutation보내서 토큰 받은 뿌듯한 모습 ㅋ 0 ㅋ
한시간이 지나면 그럼 만료가 되나 ?
-자동으로 연장이 되는 refreshToken이라는것이 있는데, 그것은 난이도가 좀 어려워서 나중에 다시 !
내가받은 토큰으로 jwt가서 검색을 해보니....! 나오네!
저기 payload에서 알수있는 부분은
iat ~ exp 차이가 3600 (60*60) 한시간동안 가능한 토큰임을 알려주는부분 !- 근데 이게 이렇게 다열리면 어떡해...? jwt토큰은 누구나 볼수있다..! 조작은 불가!
- jwt안에는 중요한 정보를 저장하면 안된다 !!
그래서 밑에 있는 부분이 signature (처음에 만들어졌을때 서명된 내용이 있다 )
조작이 되었는지, 안되었는지 판별가능 !- 비밀번호는 암호화되어 저장된다
- 양방향 암호화
다시 원상복구도 가능하고, 복호화도 가능한 암호화! (그래서 안씀)- 단방향 암호화
특정 비밀번호를 암호화하게되면, 복호화가 불가능한 케이스를 의미한다 !
-복호화가 어떻게 안되는거지 ? (hash)
hash방식을 적용해서 (17혹은 27혹은 37혹은..다양한 비번이 7로 암호화되어있다는 ? 의미)
근데 또 해커들은 레인보우테이블을 이용해서! 해킹을 할수가 있어..
완벽한 보안도 완벽한 해킹도 없따..
아니 근데 해커 좀 미워 갑자기.
- 로그인 인증 토큰은 어디에 저장해 ?! (Recoil)
- createproduct나 다양한 뮤테이션 보낼때 그럼 토큰은 어떻게 보내지 ?
- http 헤더 부분을 봐보자, 어디서 api요청인지...등등 다양한 정보가 나오는데 !
- request headers여기 부분에다가 access token을 첨부해서 보내주는거다 !
이렇게 api에서 headers부분에 토큰을 넣어줘야 확인가능하다 !
- "Authorization" : "Bearer" - basic..이런것도 있지만 데이터가 어떤것인지 알려줄떼 ..!
문자열이다....bearer은 토큰이다! 라고 알려주는 관례적인 표현이다 !- 인증(Authenticatoion)
: 로그인 api를 의미, 내 이메일과 비번을 넘겨서 토큰을 받아오는 과정
- 인가(Authorization)
: 이미 받은 토큰을 가지고, 상품을 등록하거나 내 정보를 받아오거나 결제를 하는 api를 사용할때 토큰을 가지고 본인확인을 하는 과정
- 인증은 한 번 (가입할때) 하게 되고, 이용할때는 인가를 자주 하게 된다!
- 로그인과 Context-API
- 두 페이지를 만들어보자 !
-이메일, 비번, 로그인하기 페이지에서 버튼을 클릭해서 login api요청해서
받아오는 데이터(access Token)를 state에 저장
(모든 페이지 사용해야하니까 global한 recoilstate)
로그인 완료페이지로 이동해서
받아온 이름..등등 조회할수 있도록 !
그런데, 우리가 하는 방식대로 recoilstate 빼준거 불러왔는데 성공완료 페이지에서 데이터가 안떠 ! 그 이유는 뭘까 !
페이지 전역 설정을 해주는 app.tsx에서 accessToken을 받아와서 내려줘야하기 때문이다.
그래서 그 김에, 지저분했던 app.tsx의 apollosetting 부분을 컴포넌트로 빼서 소스코드를 따로 관리해주고, recoil만 딱 보이게 가져다가 쓰자 !
자기 자식에서만 사용해야하는 라이브러리들이 많아 ! 그럼 어떻게 할까 ?
해당하는 recoilroot를 일단 return 가장 바깥쪽으로 빼주고
안에 apolloprovider를 빼줄게 !
최종적으로 연결한 파일 !