[TIL] 로그인 1

우기·2023년 4월 13일
1
post-thumbnail

📒 오늘 공부한 내용

🔍수업목차

[22-1] 로그인의 역사
[22-2] 단방향 암호화(해싱) 양방향 암호화
[22-3] 토큰을 같이 보내주는 방법
[22-4] 로그인과 Recoil 연결

✅ 로그인의 역사


📂 첫번째 로그인의 역사

  • 브라우저에서 특정 email과 password를 가지고 로그인을 하게되면 백엔드로 loginAPI 요청이 날라가게 된다.
  • 백엔드에서는 해당 유저가 있는지 DB에 확인 후 있으면 session에 저장
  • 특정한 id를 부여해서 브라우저로 보내준다.
  • 보내진 id는 해당 유저가 뭔가를 요청할 때 본인이 누군지 식별 할 수 있도록 id를 함께 넣어서 보내준다.

🎯 한계
이렇게 유저의 정보(Id)를 백엔드 서버로 받다보니 한번에 여러명의 정보를 받기엔 한계가 있다.
이를 보완하기 위해서 백엔드 컴퓨터를 scale-up을 해주었다.

scale-up : 컴퓨터의 성능(cpu,memory 등)을 올려주는 것

📂 두번째 로그인의 역사

  • 백엔드 컴퓨터의 성능을 올려주었음에도 불구하고 더 많은 유저의 접속이 동시 다발적으로 일어나면, 여전히 서버의 부하를 초래했다.
  • 그래서 나온 방법으로 백엔드 컴퓨터를 복사하는 방법
  • 유저의 정보가 담기는 백엔드 컴퓨터를 복사해 여러대의 컴퓨터로 백엔드 서버의 부하를 분산해주었다.

🎯 한계

  • 백엔드 컴퓨터를 복사할때 세션까지 scale out이 안되기때문에 기존의 로그인 정보를 가지고 있던 백엔드 컴퓨터가 아니면, 로그인 정보가 없다.
  • 백엔드 컴퓨터를 복사해도 DB는 하나이기 때문에 결국 DB로 부하가 몰리는 병목현상이 일어난다.

scale-out : 똑같은 성능의 컴퓨터를 추가하는 것

📂 세번째 로그인의 역사

  • session을 scale-out해오지 못하는 문제점을 보완한 방법이자 DB에 부하가 몰리는 병목현상을 해소한 방법으로, 현재 많이 쓰이고 있는 방법이다.
  • DB를 복사하는 방법은 비용문제가 발생하기 때문에 비효율적이다.
  • 따라서 위의 문제점은 데이터를 쪼개면서 해결하게 된다.

💡 데이터베이스를 쪼개는데는 2가지의 방법

  • 수직파티셔닝
  • 수평파티셔닝(샤딩)

🎯 한계

  • DB는 컴퓨터를 껏다 켜도 날아가지 않기 때문에 데이터들이 disk에 저장된다.
  • 안전하지만 느리다. 이렇게 disk에 저장된 데이터를 추출해 오는 현상을 DB를 긁는다고(scrapping) 표현한다.
  • 이를 해결하기위한 방법으로 Redis라는 메모리에 저장하는 임시 데이터베이스에 저장해둔다. redis는 메모리에 저장하기 때문에 디스크 보다 빠르다.
  • 저장된 특정 ID(토큰)을 다시 브라우저로 돌려주게 되고 돌려받은 토큰은 브라우저 저장공간에 토큰을 저장해두고 어떤 행동을 할때 토큰을 같이 보내주어 사용자가 누구인지 식별한다.

Redis : 메모리에 저장해두는 임시 데이터 베이스

📂 네번째 로그인의 역사

  • 사람들은 “로그인 정보를 굳이 서버나 DB에 저장해야 할까?” 생각한다.
  • 그렇게 탄생한 것이 JWT 토큰이다.
  • JWT 토큰은 유저 정보를 담은 객체를 문자열로 만들어 암호화한 후 암호화된 키(accessToken)를 브라우저에 준다.
  • 받아온 암호화된 키는 브라우저 저장소에 저장해두었다가 유저의 정보가 필요한 API를 사용할 때 보내주게 되면,해당 키를 백엔드에서 복호화해서 사용자를 식별한 후 접근이 가능하도록 한다.
  • JWT 토큰에는 해당 토큰이 발급 받아온 서버에서 정상적으로 발급을 받았다는 증명을하는 signature를 가지고 있다.
  • 따라서 사용자의 정보를 DB를 열어보지 않고도 식별할 수 있게 되었다.

✅ 단방향 암호화(해싱) 양방향 암호화


  • 비밀번호나 계좌번호같은 민감한 정보는 백엔드에 저장할때 그대로 저장하지 않는다.

📂 양방향 암호화

  • 양방향 암호화는 JWT같은 복호화가 되는 암호화를 말한다.
  • 암호화와 복호화 모두할 수 있는 암호화

📂 단방향 암호화(hash)

  • 단방향 암호화는 암호화는 되지만 복호화는 안되는 것을 의미한다.

    275719  — 암호화 —> 779
  • 0으로 나눴을 때 나머지가 7이 되는 숫자는 27,37,47 등등 너무나도 많기 때문에 원래 정보가 뭔지 모르게 만드는 것

  • 민감한 정보를 저장할때는 해킹을 당해도 알아볼 수 없도록 단방향 암호화를 사용하여 저장하게 된다.

💡 authentication과 authorization

  • authentication(인증) : 로그인을해서 토큰을 받아오는 과정
  • authorization(인가) : 리소스에 접근 할 수 있도록 토큰을 확인하는 과정

✅ 토큰을 같이 보내주는 방법


  • http header에 토큰을 넣어서 보내주면 된다.

  • HTTP HEADERS에 “Authorization”을 보내주면 된다.
  • 위에서 사용되는 Bearer는 관례상 사용하는 것 일 뿐 반드시 사용하건 아니다.

✅ 로그인과 Recoil 연결


login 폴더

import { gql, useMutation } from "@apollo/client";
import { useState } from "react";
import type { ChangeEvent } from "react";
import type {
  IMutation,
  IMutationLoginUserArgs,
} from "../../../src/commons/types/generated/types";
import { useRouter } from "next/router";
import { useRecoilState } from "recoil";
import { accessTokenState } from "../../../src/commons/stores";

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

export default function LoginPage(): JSX.Element {
  const router = useRouter();
  const [, setAccessToken] = useRecoilState(accessTokenState);

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [loginUser] = useMutation<
    Pick<IMutation, "loginUser">,
    IMutationLoginUserArgs
(LOGIN_USER);

  const onChangeEmail = (event: ChangeEvent<HTMLInputElement>): void => {
    setEmail(event.currentTarget.value);
  };

  const onChangePassword = (event: ChangeEvent<HTMLInputElement>): void => {
    setPassword(event.currentTarget.value);
  };
  const onClickLogin = async (): Promise<void> => {
    try {
      // 1. 로그인 뮤테이션 날려서 accessToken 받아오기
      const result = await loginUser({
        variables: { email, password },
      });
      const accessToken = result.data?.loginUser.accessToken;
      console.log(accessToken);

      // 2. 받아온 accessToken을 globalState에 저장하기
      if (accessToken === undefined) {
        alert("로그인에 실패했습니다! 다시 시도해 주세요!");
        return;
      }
      setAccessToken(accessToken);

      // 3. 로그인 성공 페이지로 이동하기
      void router.push("/section23/23-01-login-success");
    } catch (error) {
      if (error instanceof Error) alert(error.message);
    }
  };

  return (
    <>
      이메일: <input type="text" onChange={onChangeEmail} />
      비밀번호: <input type="password" onChange={onChangePassword} />
      <button onClick={onClickLogin}>로그인</button>
    </>
  );
}

login-success 폴더

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

const FETCH_USER_LOGGED_IN = gql`
  query {
    fetchUserLoggedIn {
      email
      name
    }
  }
`;

export default function LoginPage(): JSX.Element {
  const { data } =
    useQuery<Pick<IQuery, "fetchUserLoggedIn">>(FETCH_USER_LOGGED_IN);

  return <>{data?.fetchUserLoggedIn.name}님 환영합니다!</>;
}

global state 폴더

import { atom } from "recoil";

export const countState = atom({
  key: "countState",
  default: 0,
});

export const accessTokenState = atom({
  key: "accessTokenState",
  default: "",
});

apollo 폴더

import {
  ApolloClient,
  ApolloLink,
  ApolloProvider,
  InMemoryCache,
} from "@apollo/client";
import { createUploadLink } from "apollo-upload-client";
import { useRecoilState } from "recoil";
import { accessTokenState } from "../stores";

const GLOBAL_STATE = new InMemoryCache(); // 컴퓨터 메모리에다가 백엔드에서 받아온 데이터 모두 임시로 저장해놓기 => 나중에 알아보기

interface IApolloSettingProps {
  children: JSX.Element;
}

export default function ApolloSetting(props: IApolloSettingProps): JSX.Element {
  const [accessToken] = useRecoilState(accessTokenState);

  const uploadLink = createUploadLink({
    uri: "http://backend-practice.codebootcamp.co.kr/graphql",
    headers: { Authorization: `Bearer ${accessToken}` },
  });

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

  return (
    <>
      <ApolloProvider client={client}>{props.children}</ApolloProvider>
    </>
  );
}

_app.tsx 폴더

import "../styles/globals.css";
import type { AppProps } from "next/app";
import Layout from "../src/components/commons/layout";
import ApolloSetting from "../src/commons/apollo";
import { Global } from "@emotion/react";
import { globalStyles } from "../src/commons/styles/globalStyles";
import { RecoilRoot } from "recoil";

export default function App({ Component }: AppProps): JSX.Element {
  return (
    <div>
      <div>===== 여기는 _app.js 컴포넌트 시작부분 입니다. =======</div>
      <RecoilRoot>
        <ApolloSetting>
          <>
            <Global styles={globalStyles} />
            <Layout>
              <Component />
            </Layout>
          </>
        </ApolloSetting>
      </RecoilRoot>
      <div>===== 여기는 _app.js 컴포넌트 마지막부분 입니다. =======</div>
    </div>
  );
}

💡 브라우저 저장소의 종류와 특징

  • 쿠키(COOKIE) : 영구 저장이 가능하며, 만료시간을 지정 할 수 있다.
  • localStorage : 영구 저장이 가능하며 ⇒ 임시로 사용할 저장소
  • sessionStorage : 브라우저 종료시 사라진다.
profile
개발 블로그

0개의 댓글