!! 이미지, 로그인, 배포 등은 가장 중요한 과정이니 프로세스를 잘 이해하는게 좋을 것 !!

로그인 프로세스

로그인이 어떻게 이루어지는지 과정을 알아보는 것이 중요하다

  1. 브라우저에서 프런트엔드에 요청하면 HTML, CSS, JS를 가져온다
  2. 데이터가 필요한부분은 백엔드로, 백엔드에서 DB를 거쳐가며 가져온다

그럼 상품등록에서 로그인이 된 사람만 하고싶다면?

브라우저에서 백엔드로 createProduct를 요청했을때
백엔드에서는 로그인이 되었는지 확인해야한다.

옛날에는 이걸 어떻게 구별했는가
브라우저에서 ID/PW로 로그인 했다면 백엔드에 ID, 언제로그인함 이런식으로 기록해둠
(이런거를 메모리세션(session)이라고 부름)
그 이후에는 이 정보를 이용해서 글작성이나, 로그인하지 않은사람에게 메세지를 보냄. 근데 여기서 문제점이 뭐였는가?
사람들이 접속할때마다(트래픽이라고 함)백엔드 컴퓨터에 메모리에 저장됨, 근데 메모리가 다 차게되면 더이상 정보를 저장할 수 없음
-> 저장공간인 메모리를 늘려줘야함, 속도를 위해 cpu도 교체(scale up)
-> 근데 언제까지 이럴수가 없으니까 똑같은 정보를가진 백엔드 서버를 여러개를 가져와서 처리할 수 있게함 이걸 statepull 이라고 함

-> 근데 내 로그인 기록이 남아있는 백엔드컴퓨터가 아니라면 로그인했는지 인식하지 못함(정보는 같아도 처리는 다르기때문)
-> 그럼 세션(로그인 했던 정보)를 DB에 넣는다면? 여기서는 사실 Backend 컴퓨터만 추가하면 어떤 백엔드 컴퓨터를 쓰던 로그인해도 똑같음 이걸 stateless라고함

-> 이제는 데이터베이스가 병목이됨 메모리가 무한이 아니기 때문에 백엔드 컴퓨터는 늘려도 디비를 늘리기가 힘들어짐
-> 그럼 DB도 여러개 만들면 되는거 아냐?
-> 우리 DB짱큰데 그걸 어떻게 복사해

인증테이블 파티셔닝

-> DB 테이블 나눠서 보관하자 파티셔닝이라고 하자, Partitioning

-> 그래서 회원정보는 User이라고 보관하고 포인트랑 계좌는 UserPayment로 너가 보관해(수직파티셔닝)
-> 1번부터 1000번 까지는 1번 DB, 2000번부터 3000번까지는 2번 DB, 3000번부터 4000번까지는 3번 DB에나눠서 보관하자(수평 파티셔닝, 데이터베이스 샤딩)
-> 그럼 이제 어떻게 나눠서 보낼래? 한 컴퓨터에 한 명씩 순서대로 받을래? (RoundRobin)
-> 아니면 가장 접속 적은컴퓨터에 받을래?
-> DB어떤거 쓸지는 Redis로 구별하자
-> 작은 엑셀같은건데 디스크 말고 메모리에 잠깐만 저장해서 백엔드에서 디비로 요청하면 디비 쓰지 말고 Redis를 쓰자.
-> 결국 DB에 가져와서 데이터를 꺼내와야함, 이를 DB를 긁는다 - 라고 함
-> 근데 만약에 DB를 안긁어도 된다면?

JWT토큰


-> JWT토큰의 등장(아직안함)
-> 브라우저에서 백엔드로 아이디/패스워드로 넣음
-> DB로 로그인됐다고 정보를 넣으면 랜덤 글자로 된 아이디를 줌(이걸 토큰이라고 함, 여기서는 인증에 쓰니까 인증토큰)
-> 인증토큰을 백엔드로 전송한것
-> 토큰을 브라우저에 저장해둠, 위치는 글로벌 스테이트나 브라우저의 내부 저장소(다음에 또배울것, localStroage, sessionStorage, cookie, state 등이 있음)
-> 일단 myToken이라는 state에 넣자
-> createProduct할때 name, price... myToken도 보내!
-> 그럼 백엔드에서 로그인 했는지 안했는지 판단함
-> 이게 그냥 토큰이었고 이제 진짜 JWT 토큰 등장함
(여기서 프런트엔드에서는 토큰을 myToken을 저장, myToken을 백엔드에 넘겨주는 일을 하면 됨 그래도 구조와 흐름을 알아야 문제를 다룰때나 백엔드 개발자와 이야기 할 수 있음)
-> 그냥 토큰은 뭐 하나 할때마다 디비를 긁어야되고, 트래픽이 증가하고, 문제가 많은듯해
-> JSON WEB TOKEN - JWT 쓰자
-> 뭐가 달라?

-> 철수가 있다고 하자 ㄱ-> a ㄴ-b 이렇게 교체한다고하면 철수는 철수가 뜻하는 알파벳(암호화), 그걸 반대로 돌리면 철수가 될꺼잖아 (복호화)
-> JSON 이면 이제 { 이름 : "철수", 로그인 기간: "2021.10.31"} 이렇게 되어있는데
-> 이거 암호화시키면 알수없는 문자 이걸 JWT라고 해(더 많은 과정이 있지만 간단히 설명함)
-> 물론 복호화 시키면 바로 철수랑 로그인기간도 나오게됨
-> 이러면 토큰 자체에 로그인 정보가 들어있으니까 백엔드에서 DB를 갈 필요가 없게됨
-> 그럼 이제 어떻게 하는거야?

-> 브라우저에서 아이디/비밀번호 넣으면
-> 백엔드에서 JWT토큰으로 줘
-> 그럼 브라우저에는 myToken state로 JWT 저장해
-> 이제 상품 올릴 때(createProduct) myToken도 같이 백엔드로 보내
-> 백엔드에서 복호화 하면 로그인한 정보가 나와 DB에서 로그인 했는지 확인 안해도 됨👍
-> 그리고 상품정보는 DB로 보내자

JWT토큰의 한계점

JWT는 백엔드에 들어가서 접속했는지 안했는지만 구별하기 때문에 중간에 탈취해도 이 정보가 맞는지 DB랑 검증할 수 가 없음(DB를 안긁으니까)
-> 만료시간을 일반 토큰보다 줄이자(30분~2시간)
-> 섞어서 쓰자 처음 1번은 DB에 기록해
-> 시간 다 지나서 만료되면 자동 재발급할때도 기록하자(리프레쉬토큰)
-> 보안이 중요하면 redis붙여서, 트래픽 절약할라면 JWT방식 쓰자

토큰 보내는 과정 상세보기


회원가입을 플레이그라운드에서 해보자


이렇게 로그인하면 로그인 정보가 토큰으로 변환되어서 나온 것을 볼 수 있다. 이렇게 긴 이유는 정보를 변환했기 때문이다.

이제 이 정보를 이용하려면???

(토큰이 없으면 이런거 요청해도 안된다)

HTTP HEADERS에 "authorization": "Bearer + 토큰"
Bearer는 관례에 따라 쓴 것으로 이 용도가 뭔지 쓴 것이다

(토큰을 넣으니까 된다)

이 HTTP HEADERS에 로그인 정보를 넣어서 적용하는 방법은 아주 중요하고 어디서든 쓰이니 잘 알아둬야함
!! 토큰 사용시 유의점

jwt.io
JWT토큰을 풀어서 보여주는 사이트이다. 이처럼 아이디, 사용기간날짜, 사용용도 이런거를 사실상 아무나 볼 수 있기 때문에 , 민감한 정보는 이 토큰 안에 넣지 않는 것이 좋다. 하지만 조작은 안되는데, 만들때 key를 통해서 만드는데 조작시 백엔드에서 검증하기 때문에 걸린다

실제로 코드에서는 어따쓸까??
apollo를 설정하는 페이지에 넣어주면된다

해싱

회원정보를 저장할때 이메일, 이름, 암호를 저장한다.
근데 비밀번호같은거는 그냥 저장했다가는 유출되고, 보통 비밀번호 같이 쓰니까 그러면.. 큰일난다
-> 비밀번호를 입력하면 테이블에는 암호화된 글자를 저장해줌
1234라고 입력 -> abcd라고 저장
-> 못털꺼같지? 또 되돌리는 테이블 가지고다녀서 다 바꿔봄
-> 암호화는 이제 두종류
-> 양방향 암호화 : 1234 - abcd -> 다시 쓸때는 abcd - 1234
-> 단방향 암호화 : 1234 - $% (근데 이 공식을 거쳐가면서 다른 숫자도 $%가 나올 수 있음) 이 값만으로는 테이블로 되돌릴 수는 없음, 원본이 뭔지 구별 할 수 없음
이 과정을 해싱한다 password hash 라고함

로그인과 Context-API

실제 코드로 구현하기

app.tsx(글로벌 스테이트를 수정해야한다.)

import { Global } from "@emotion/react";
import {
  ApolloClient,
  ApolloProvider,
  InMemoryCache,
  ApolloLink,
} from "@apollo/client";
import "antd/dist/antd.css";
import { AppProps } from "next/dist/shared/lib/router/router";
import Layout from "../src/components/commons/layout";
import { globalStyles } from "../src/commons/styles/globalStyles";
import { createUploadLink } from "apollo-upload-client";

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries


// Initialize Firebase
export const firebaseApp = initializeApp(firebaseConfig);

export const GlobalContext = createContext(null);

function MyApp({ Component, pageProps }: AppProps) {
  const [myAccessToken, setMyAccessToken] = useState("");
  const [myuserInfo, setMyUserInfo] = useState({});
  const myValue = {
    accessToken: myAccessToken,
    setMyAccessToken: setMyAccessToken,
    userInfo: myuserInfo,
    setMyUserInfo: setMyUserInfo,
  };

  const uploadLink = createUploadLink({
    uri: "백엔드주소",
  });

  const client = new ApolloClient({
    link: ApolloLink.from([uploadLink as any]),
    cache: new InMemoryCache(),
  });

  return (
    <GlobalContext.Provider value={myValue}>
      <ApolloProvider client={client}>
        <Global styles={globalStyles} />
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </ApolloProvider>
    </GlobalContext.Provider>
  );
}

export default MyApp;

useContext를 통해서 로그인페이지에 임포트해옴, 그 외 로그인 페이지 구현

import { ChangeEvent, useContext, useState } from "react";
import { GlobalContext } from "../_app";
import { gql, useMutation } from "@apollo/client";
import {
  IMutation,
  IMutationLoginUserArgs,
} from "../../src/commons/types/generated/types";
import { useRouter } from "next/router";

const LOGIN_USER = gql`
  mutation loginUser($email: String!, $password: String!) {
    loginUser(email: $email, password: $password) {
      accessToken
    }
  }
`;

export default function loginPage() {
  const router = useRouter();
  const { setAccessToken } = useContext(GlobalContext);
  const [myEmail, setmyEmail] = useState("");
  const [myPassword, setmyPassword] = useState("");
  const [loginUser] = useMutation<
    Pick<IMutation, "loginUser">,
    IMutationLoginUserArgs
  >(LOGIN_USER);

  function onChangeMyEmail(event: ChangeEvent<HTMLInputElement>) {
    setmyEmail(event.target.value);
  }

  function onChangeMyPassword(event: ChangeEvent<HTMLInputElement>) {
    setmyPassword(event.target.value);
  }

  async function onClickLogin() {
    const result = await loginUser({
      variables: {
        email: myEmail,
        password: myPassword,
      },
    });
    setAccessToken(result.data?.loginUser.accessToken); // 여기서 setAccessTocken 필요! (글로벌 스테이트에)
    // 로그인 성공된 페이지로 이동하기
    router.push('/22-02-login-success')
  }

  return (
    <>
      <div>
        아이디 : <input type="text" onChange={onChangeMyEmail} />
      </div>
      <div>
        비밀번호 : <input type="password" onChange={onChangeMyPassword} />
      </div>
      <div>
        <button onClick={onClickLogin}>로그인하기!! </button>
      </div>
    </>
  );
}

로그인이 성공했는지 보여주는 화면을 만들고 router.push로 연결,
성공화면 만들기

export default function LoginSuccessPage(){

    return(
        <>
        <div>로그인에 성공하였습니다 !</div>
        </>

    )
}

이후 항상 인증을 할 때 header도 넣어야한다.
app.tsx를 수정해야한다.

  const uploadLink = createUploadLink({
    uri: "백엔드 주소",
    headers:{authorization: `Bearer ${myAccessToken}`}
  });

이후 로그인 성공페이지에서 로그인 정보를 불러와서 이름까지 표시를 해보자

import { gql, useQuery } from "@apollo/client";
import { IQuery } from "../../src/commons/types/generated/types";

const FETCH_USER_LOGGEDF_IN = gql`
  query fetchUserLoggedIn {
    fetchUserLoggedIn {
      email
      name
      picture
    }
  }
`;

export default function LoginSuccessPage() {
  const { data } = useQuery<Pick<IQuery, "fetchUserLoggedIn">>(
    FETCH_USER_LOGGEDF_IN
  );
  return (
    <>
      <div>로그인에 성공하였습니다 !</div>
      <div>{data?.fetchUserLoggedIn.name}님 환영합니다!!!</div>
    </>
  );
}
profile
개발자 새싹🌱 The only constant is change.

0개의 댓글