커리큘럼
- 로그인을 이해하려면 역사를 알아야해!!! => Login
- JWT 토큰?? 이건 또 뭐시여?? => JWT
- 암호화 방식은 2가지가 있다?? 알고 있었나?? => Encrypt / Hash
- 로그인 인증 토큰은 어디에 저장해?? => Context-API
우리가 로그인에 대해 알아보기전 어떠한 방식으로 로그인이 이뤄지는지에 대해 알아보아야 한다.
먼저 우리는 브라우저라는 하나의 웹에서 백엔드가 요청을 보내게 된다.
프론트엔드에서 백엔드에게 로그인에 대한 정보를 넘기고 백엔드에서는 그에 대한 정보를 DB에 저장해준다.
저장해 준뒤에는 각각의 상태가 존재하는데 먼저 statful상태와 stateless 상태가 있는데 현재 백엔드에서
상태가 없는 상태로써 여러개의 테이블로 수평 확장이 가능하였다.
하지만 하나의 서버로 데이터를 보내주는 것이 바로 문제인데 그 문제를 해결하기 위해서는
DB에서 여러로 쪼개서 보내주면 문제를 해결할 수 있었다.
하지만 이런 방식에서도 DB를 쪼개주어야 하는데 만약 DB를 쪼개준다면 발생하는 문제점이
하나의 DB의 100만건의 회원 정보가 담긴다면 다른 DB에서도 동일하게 100만건이 일치해야한다.
DB가 하나만 바뀌더라도 전부다 동일해야한다. 그렇기 때문에 그만큼의 비용이 발생하는데
그것을 해결하기 위해 데이터를 쪼개주는 방식을 택하면 되었다.
로그인에 대한 유저정보를 표형태 테이블로 담기 위해 수직 파티셔닝 방법으로 table을 두가지로 나눠서
하나의 테이블에는 1~100가지의 유저정보를 101부터 200까지는 table2의 대한 정보를 담아준다.
유저가 로그인을 성공한다면 백엔드에 정보가 담기는데, 로그인을 성공한다면 토큰 이라는 값이 생긴다.
만약 철수라는 유저로 로그인의 성공한다면 state와 쿠키,localStoate에 대한 정보가 담기는데
이 정보를 백엔드에게 createPayment 토큰을 보내준다. 그러면서 백엔드는 방금 말한 표형태의
테이블 방식으로 유저정보가 담긴 테이블을 쪼개주개 된다.
하지만 여기서도 발생하는 문제점은 결국 이 정보는 Disk에 담기게 되는데 DB를 긁어주는 시간이
오래걸린다는 단점이 있다.
조금 더 빨리 하는 방법은 검색구현식을 이용한 Redis를 사용하는 것이다.
유저가 로그인을 한다면 로그인에 대한 정보를 암호화를 시켜주는데 그것을 바로 인코디드 함호화라고 하며
암호화를 해준뒤에는 복호화를 해준다.
로그인 User에 대한 토큰이 기입될때 나오는 Payload와 Header에 대한 정보이다.
Header부분은 로그인시 받아온 Header 안에 알고리즘에 대한 정보가 담긴다.
Payload는 로그인시 받아온 정보를 객체화 한 것이다.
payload를 보면 로그인한 id와 iai부분은 언제 생성되었는가이고 exp는 만료기간이다.
JWT토큰은 누구인지 열어볼 수 있기 때문에 중요한 정보를 저장해서는 안된다.
만약 이런 토큰을 훔쳐와서 브라우저에 저장한뒤 JWT에 입력하게 된다면 남의 행사를 할 수 있기 때문이다.
백엔드에서는 어떤 유저의 정보인지 알 수 있는 방법이 있다.
그래서 토큰을 만약 탈취당했다면 토큰을 탈취한 사람이 탈취를 당한 사람의 행세를 할 수 있다는 것이다.
그래서 보통 로그인을 어떻게 하나에 따라 만료시간을 1시간 단위로 짧게 주는 이유도 바로 이 문제 떄문
약 1시간 정도의 사용기간을 주거나 길면 2시간 짧다면 30분 정도의 만료시간을 짧게 준다.
그래서 혹시 탈취를 당한다 하더라도 30분뒤에 만료가 되기 때문에 탈취에 대한 위험성은 없다고 볼 수 있다.
그런데 우리가 토큰에 대한 만료기간을 알 수 있는데, 그래서 이 만료기간을 내년까지 조정해 만료기간을 설정할 수
있을 듯하다.
하지만 그렇게 되면 JWT 토큰을 쓸 필요가 없다.
JWT 토큰을 만들때 서명이 이뤄지는데, 서명에필요한 비밀번호를 작성해서 백엔드에게 저장해주고
그것을 가져와서 서명한다고 표현한다.
데이터를 모두 묶어 서명처리를 해준다. 그래서 토큰안에 내용을 누구든 볼 수는 있으나 조작할 수는 없다. 조작하기 위해서는 서명할 때 사용한 키가 필요하다.
그래서 그 키를 가지고 서명이 조작되었는지에 대해 체킹이 가능하다.
조작은 불가하나 안에 내용은 그 누구도 볼 수 있다.!!!
그래서 JWT내용에는 중요한 정보를 넣는 것을 삼가한다.
로그인을 한 기준으로 토큰 만료기간은 1시간 단위이다. 그런데 1시간 단위로 한다면 다시 로그인을 해줘야한다.
이것을 refeshToken을 사용해 해결해주면 된다.
acessToken을 사용해 1시간 마다 인증해 로그인 하는 방식도 있다.
결론은 이렇게 인증하는 방식은 Authentication이라고 한다.
그 다음 부터 해줘야 할 작업은 토큰을 가지고 createPayment,updateProfile이 필요하다.
토큰을 보낼때 jwt 방식으로 보내준다.
그럼 백엔드에서는 token을 가지고 사용자를 인식해준뒤 그 이름에 상품을 하나 등록해준다.
사용자는 인식하는 부분에서 Authorization이라 부른다.
인증은 Authentication
인가는 Authorization으로 나뉜다.
인증을하고 토큰을 받아올때 이것을 가지고 특정권한을 사용해서 상품을 등록해줘야 한다.
http Header에 요청시 로그인한 유저의 요청 정보를 보내줄 수 있다.
JWT 토큰을 우리가 AcessToken으로 사용하는 것일 뿐이다.
다른 용도의 Token도 사용이 가능하다.
const [accessToken, setAccessToken] = useState('');
const value = {
accessToken,
setAccessToken,
};
토큰에 대한 state값을 저장해준다. 로그인시 들어간 token이 필요한데 value 값에 넣어준다.
<GlobalContext.Provider value={value}>
<ApolloProvider client={client}>
<Global styles={globalStyles} />
{/* <Layout> */}
<Component {...pageProps} />
{/* </Layout> */}
</ApolloProvider>
</GlobalContext.Provider>
GlobalContext.Provider에 value 값을 넣어준다.
// 이 토큰 자리에 로그인한 토큰이 들어가야함
headers: { Authorization: `Bearer ${accessToken}` },
로그인시 토큰을 넣어줄 headers가 필요하다.
import { gql, useMutation } from '@apollo/client';
import { Modal } from 'antd';
import { useRouter } from 'next/router';
import { ChangeEvent, useContext, useState } from 'react';
import { IMutation, IMutationLoginUserArgs } from '../../src/commons/types/generated/types';
import { GlobalContext } from '../_app';
const LOGIN_USER = gql`
mutation loginUser($email: String!, $password: String!) {
loginUser(email: $email, password: $password) {
accessToken
}
}
`;
export default function Login() {
const { setAccessToken } = useContext(GlobalContext);
const router = useRouter();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// PICK은 유틸리티
// Omit이라는게 있는데 이거는 반대의 의미
// 빼고 나머지를 가져온다는 뜻
// Omit => 특정 데이터 뺴고 나머지 가져와줘
// Partial == 그대로 다 가져오나 뒤에 ?를 추론해서 가져옴
const [loginUser] = useMutation<Pick<IMutation, 'loginUser'>, IMutationLoginUserArgs>(LOGIN_USER);
const ChangeEmail = (e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};
const ChangePassword = (e: ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
};
const btnClickLogin = async () => {
if (email === '') {
setEmail('이메일을 입력해주세요');
} else {
setEmail('');
}
if (password === '') {
setPassword('비밀번호를 입력해주세요');
} else {
setPassword('');
}
try {
const result = await loginUser({
variables: {
email: email,
password: password,
},
});
console.log(result.data?.loginUser.accessToken);
if (setAccessToken) {
setAccessToken(result.data?.loginUser.accessToken || '');
}
// 로그인 성공 페이지 이동
router.push('/22-02-success');
} catch (error) {
if (error instanceof Error) Modal.error({ content: error.message });
console.log(error);
}
};
return (
<>
<div>
<div>로그인 페이지</div>
<input type="text" placeholder="이메일" onChange={ChangeEmail} />
{email ? <span></span> : <span>이메일 주소를 입력해주세요</span>}
<input type="password" placeholder="비밀 번호" onChange={ChangePassword} />
{password ? <span></span> : <span>비밀번호 입력</span>}
<button onClick={btnClickLogin}>로그인</button>
</div>
</>
);
}
'etc. > TIL' 카테고리의 다른 글
[TIL] 2022년 02월 15일 - withAuth와 SessionStorage / Cookie (0) | 2022.02.15 |
---|---|
[TIL] 2022년 02월 11 - Global-State (0) | 2022.02.12 |
[TIL] 2022년 02월 09일 - 검색기능 추가, 쓰트롤링 디바운싱 (0) | 2022.02.09 |