[22-1] 로그인의 역사
[22-2] 단방향 암호화(해싱) 양방향 암호화
[22-3] 토큰을 같이 보내주는 방법
[22-4] 로그인과 Recoil 연결
📂 첫번째 로그인의 역사
🎯 한계
이렇게 유저의 정보(Id)를 백엔드 서버로 받다보니 한번에 여러명의 정보를 받기엔 한계가 있다.
이를 보완하기 위해서 백엔드 컴퓨터를 scale-up을 해주었다.
scale-up
: 컴퓨터의 성능(cpu,memory 등)을 올려주는 것
📂 두번째 로그인의 역사
🎯 한계
- 백엔드 컴퓨터를 복사할때 세션까지 scale out이 안되기때문에 기존의 로그인 정보를 가지고 있던 백엔드 컴퓨터가 아니면, 로그인 정보가 없다.
- 백엔드 컴퓨터를 복사해도 DB는 하나이기 때문에 결국 DB로 부하가 몰리는 병목현상이 일어난다.
scale-out
: 똑같은 성능의 컴퓨터를 추가하는 것
📂 세번째 로그인의 역사
💡 데이터베이스를 쪼개는데는 2가지의 방법
수직파티셔닝
수평파티셔닝(샤딩)
🎯 한계
- DB는 컴퓨터를 껏다 켜도 날아가지 않기 때문에 데이터들이 disk에 저장된다.
- 안전하지만 느리다. 이렇게 disk에 저장된 데이터를 추출해 오는 현상을 DB를 긁는다고(scrapping) 표현한다.
- 이를 해결하기위한 방법으로 Redis라는 메모리에 저장하는 임시 데이터베이스에 저장해둔다. redis는 메모리에 저장하기 때문에 디스크 보다 빠르다.
- 저장된 특정 ID(토큰)을 다시 브라우저로 돌려주게 되고 돌려받은 토큰은 브라우저 저장공간에 토큰을 저장해두고 어떤 행동을 할때 토큰을 같이 보내주어 사용자가 누구인지 식별한다.
Redis
: 메모리에 저장해두는 임시 데이터 베이스
📂 네번째 로그인의 역사
📂 양방향 암호화
📂 단방향 암호화(hash)
단방향 암호화는 암호화는 되지만 복호화는 안되는 것을 의미한다.
275719 — 암호화 —> 779
0으로 나눴을 때 나머지가 7이 되는 숫자는 27,37,47 등등 너무나도 많기 때문에 원래 정보가 뭔지 모르게 만드는 것
민감한 정보를 저장할때는 해킹을 당해도 알아볼 수 없도록 단방향 암호화를 사용하여 저장하게 된다.
💡 authentication과 authorization
authentication(인증)
: 로그인을해서 토큰을 받아오는 과정authorization(인가)
: 리소스에 접근 할 수 있도록 토큰을 확인하는 과정
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
: 브라우저 종료시 사라진다.