로그인 + 추가적인 보안강화하기
사실 로그인부분이 여기저기 쓰인다고 해도 보안부분이 계속 붙다보면은 복잡하고 가장 어려운부분이라고한다.
사실 난 로그인부분도 걱정을 하고있던상태라 복잡하다고 그래서 더 무서웠다.
일단 로그인방식에 대해 알아보자.
(참고로 회원가입과 로그인은별개.)
회원가입을 하고 그 정보가 DB에 저장된다.
브라우저에서 정보를 입력하고 로그인버튼을 누르면 API요청이 들어가고 벡엔드에서 아이디(이메일), 비밀번호를 받아 DB에서 확인하고 있으면 있다고 보낸다. 그리고 그 유저가 로그인했다는 것을 벡엔드의 세션 메모리(메모리세션)에 저장하고 그 세션 아이드를 브라우저로 보내는 것이 인증(Authentication)즉, 로그인 과정이다.
그리고 세션아이디를 받은 브라우저에서 저장이 이루어진다.
로컬스토리지, 변수, 세션스토리지, 쿠키 등이 그 저장공간이다. 그리고 이 세션아이디를 확인해 다음의 API날릴때
벡엔드에서 세션아이디와 API를 같이 받아 메모리세션에서 검증한다. 이 과정이 인가(Authoization)이라고 하는 과정이다.
😗인증: 로그인하여 DB에 비밀번호 , 아이디(이메일)이 있는지 확인하고 메모리세션에 저장헤 세션아이디를 만들어 내는 것.
😗인가: 이미 로그인되어있는 상태에서 유저 상세보기등의 로그인된 유저정보가 필요한 API요청시 확인하는 과정(로그인한 사람이 맞는지)
인가가 필요한 API(내정보보기 등)가 있고, 인가가 필요없는 API가 있다(게시물보기등)
위에서 설명한 로그인 방식은 에전 방식이다.
현재에 이르러서는 접속량이 많아짐에따라 메모리세션의 용량 부족 현상이 일어날 수 있어 메모리를 늘리는 작업이 필요해졌다.
이 메모리를 늘리는 것을 scale-up이라고하는데 벡엔드 컴퓨터를 늘리는 방식인 scale-out이라는 방식이 있다. 컴퓨터를 늘려 나눠서 트래픽을 보낼 수 있게된다.
트래픽: 일정시간 내 흐르는 데이터 양
트랙픽은 어떻게 분산하나?
least connection : 가장 접속이 적은 컴퓨터에 접속을 몰아주자
round robin : 공평하게돌림 1번 2번 3번컴퓨터가 있으면 점속자들 순서대로 처음사람은 1번 그다음 2번 그다음 3번 그다음에 다시1번으로 접속하게 돌리는 방식
이것 외에도 여러 알고리즘이 존재한다.
그러나 이렇게 벡엔드 컴퓨터를 나누게되면 문제가 생긴다.
발 로그인메모리 세션.
첫접속한 컴퓨터에는 들어가게되어 문제는 없으나, 다른 컴퓨터로 접속하게되면 그때는 로그인 안된 상태가 된다.(그 컴퓨터 메모리 세션에는 저장되어있지 않기 때문이다.)
따라서 scale-out 가어렵다.
컴퓨터가 특정 자기만의 상태를 가지고 있기에 사용하기 어려운것이다. = stateful상태
즉, 부하를 분산하는 것은 가능하나, 로그인을 유지시킬 수 없다.
==> stateless로 변경이 필요하다.
그래서 메모리 세션을 컴퓨터에 저장하지 않고 DB에 세션전용 테이블을 만들어 사용한다.
그런데 이렇게되면 DB에 부하가 몰리게된다. = 병목현상이 일어난다.(bottle-neck: 트래픽이 몰려서 느려지는 현상)
벡엔드 ==> 소스코드만 복사해서 똑같은 프로그램을 여러 컴퓨터에서 실행시켜주는 방식으로 사용하였다.
그런데 DB는 그 안의 테이블을 전부 복사해 나눠야 한다는 것이 비효율적이다.
따라서 다른 방식으로 나눈다.
테이블의 수평파티셔닝 즉 샤딩이라는 과정을 거치면 된다.
즉 태이블을 일정유저수만큼 나눠 가로로 자르는 샤딩이라는 것을 사용하여 1~1000까지는 1번 DB, 1001~2000 까지는 2번DB.. 이런식으로 저장하면 DB로 가는 병목현상도 해결이 가능하다.
다만, 문제점:
API요청 ==> DB에서 인가 --> 매번 DB접속해 DB를 긁어와야하는데, 영구적으로 저장되는 디스크에 저장되기에 속도가 느림.
DB를 메모리에 저장하기
mapcached라는것과 redis라는 것이있는데 요즘에는 redis를 주로 사용한다.--> 메모리에 저장하니 컴퓨터를 껐다키면 사라진다.(새션정보들을 저장한다)
정리:
메모리세션에 저장하다가 트래픽이 많아지게되어 scale-out이 어려워졌다. 따라서 DB로 세션정보를 옮겼다.
DB에서는 샤딩을 통해 분산하는데, 그러나 디스크에 저장되어 느리니 빠르게 하기위해 redis라는 메모리 기반 데이터베이스에 저장하게되었다.(인가는 요청할때마다 매번해야하는데 메모리에 저장함으로써 몇배는 빨라진다)
1.redis에 세션정보 저장하기.==> 가장많이 이용하는 방식.
2.redis에도 저장 안하기. -암호화를 사용하는 방법
암호화를 사용하며 redis에도 저장하지 않는다면 더빨라질 수 있다.
암호화 알고리즘.
특정 규칙에 의해 맵핑시키는 방법이다.
1-a,
2-b
3-c
.
.
.
이런식으로나아갈때 1862를 암호화 한다면
ahbfi
가 되고, 이 암호화한것을 다시 돌려 1862로 만들어 주는 것을 복호화 라고 한다.
복잡한 알고리즘은 많이 있다.
인증부분은 회원조회가 필요하기에 반드시 DB에서 이루어진다.
그 외의 인가 부분은 객체를 암호화하여 암호화결과를 세션아이디 대용으로 사용한다. 그리고 벡엔드에서 브라우저에 저장(인증)하고 벡엔드로 보내 인가를 받아온다.
암호화 복호화를 통해 벡엔드 자체에서 인가처리가 가능해진것이다.
객체를 암호화한 세션아이디=토큰
JWT라고한다. : (JSON Web Token) JSON(객체표기법) 웹 토큰 ==> 객체 형태를 가지고 토큰을 만들었다.
그러나 객체를 암호화 하여 세션아이디로 사용하는 방식인 JWT는 약간 부족한 감이 있다. 따라서 가장 많이 사용하는 방식이 1번 로그인 방식이다.
현재 실습 벡엔드도 1번 방식으로 되어있다.
- 암호화하면 복호화 가능한 양방향 암호화
- 암호화만되고 복호화는 북가한 단방향 암호화 ==> 여러개의 값이 하나의 값을 수렴.
비밀번호의 경우에 단방향 암호화로 처리.
==hash한다 라고 표현.
헤킹을 막기위한 최선의 방식은 hash하고 임의의 문자열을 추가하는 방식이다.
그래프 큐엘에 들어가 JWT를 받아오는 것을 해보았다.
accessToken이라는 이름으로 JWT방식의 토큰을 받아오고 있었다.
실습해보기위해 먼저 회원가입이 필요하니 createUser를 한다. 이 부분은 그냥 DB에 등록만 하면되니 docs를 보고 그대로 입력해준다.
로그인:
프론트에서loginAPI를 요청하고 결과로 accessToken을 받아오는 방식이었다.(JWT방식으로 받아오게된다.)
로그인한 토큰정보 보내기.
: 네트워크탭의 body부분에는 데이터가 들어오고, header부분에는 요약정보가 들어가는데, 이부분에 토큰이 추가되어야한다.
그래프큐엘의 아래쪽에
HTTP HEADERS 부분이 있다. 거기에 토큰을 추가하면된다.
{
"Auhorization" : " Bearer 받아온 accessToken붙이기"
}
이렇게해서 API요청을 보낸다. 인증과정인 로그인은 이미 토큰을 받아오며 해놓은 상태이니 이 정보로 받아올 수 있는(인가과정) 정보불러오기를 해보았다
fetchBoardUserLogged 를 조회하면 네트워크 헤더에 request헤더 부분에 Authorization이 추가되고 :해서 "Bearer 토큰" 이렇게 들어오는것이 확인이 된다. 그러면 백엔드에서 아 토큰을 꺼내 복호화하여 인가성공으로 결과를 가져와 준다.
Bearer?
토큰 인증방식 사용시에 앞에 붙여주는 것이 관례다. Bearer 대신 Bagic을 사용하기도 한다고 한다.
대소문자를 사용하더라도 상관없다.
벡엔드에서 어차피 Authorization을 가져와 replace하여 Bearer부분을 지우게되기 때문이다.
헤더에 "Authorization":"Bearer "방식으로 추가해주어야 로그인정보를 정확하게 가져올 수 있다. ==> 토큰이 첨부되어야 유저정보가 필요한 (인가가 필요한) 요청이 나간다.
토큰 주의사항
JWT.io라는 사이트에 들어가면 Decoded부분에 header, payload,signature 부분이 나눠져 있는것을 볼 수 있다.
Header : 어떤 알고리즘 사용으로 암호화 하는지 볼 수 있다.
Payload: 데이터를 볼 수 있다.
SigNature: 비밀번호
받아온 토큰을 왼쪽에 넣어주면 정보를 볼 수 있다.
iat(발행한 시간=초단위)
exp(만료시간=30분에서 2시간정도.) 지금 쓰고 있는 그래프큐엔에서 받은 토큰 만료시간을 확인해보니 3600초 이후니까 1시간 후이다.
만료시간이 지나면 다시 로그인을 해주어야 한다.
토큰을 붙여넣기하니 그 안의 내용들을 볼 수 있었다.
이것이 JWT의 문제점이다.
따라서 중요한 내용은 저장하지 않아야한다.
SigNature로는 조작여부를 판별할 수 있다. 즉, 토큰이 임의로 작성된것인지 아니면 받아온 것인지를 알 수 있다.
방식 ==> 로그인을 진행해 accessToken 받아오기 --> 받아온 토큰을 recoilState에 넣어주기
loginSuccess폴더에서는 로그인된 accessToken을 던져 유저정보 받아오기.
토큰 받아오고 => 글로벌 스테이트에 저장해주고,==> 아폴로 세팅부분 컴포넌트에 uri아래에 Header에 들어가게 세팅해주고 ==> 다음요청부터는 헤더의 토큰을 보고 만료시간까지 확인후 벡엔드에서 복호화 ==> DB에서 꺼내 결과를 보여준다.
const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
headers: { Authorization:
Bearer ${accessToken}
}, // 모든 API에 토큰이 첨부되어 요청들어감. 토큰이 없는경우에는 빈문자열로
현재방식의 문제점:
- 엑세스 토큰 만료시간이 짧다. 지금 만료시간이 한시간이라 한시간 후에는 다시 로그인필요하다.
- 글로벌 state라는 변수에 accessToken저장한 것이기에 새로고침하면 데이터가 사라진다.(새로고침과정 --> 프론트에 다시 서버요청 => HTML,CSS,JS를 다시 받아오는것.)So, 그 안의 변수들은 다 초기화 되어 사라진다.
해야할것: 캐싱위해 _id들어가는 부분 다 추가하기