프로젝트를 하면서 빠질 수 없는 부분이 회원가입, 로그인이다. 프로젝트에 다양한 방식으로 회원가입 로그인을 구현했지만 깊이있게 이해하고 한 것은 아니라 이번 시리즈에서 로그인 관련 내용들을 정리해 보려한다.
로그인이라는 기능에 대해서 좀 더 깊게 알아보면 인증, 인가 라는 키워드가 자주 등장한다.
◼ 인증이란 ?
접속하고자 하는 사용자가 적절한지 확인하는 절차이다. 예를 들면 서비스의 가입을 위해 "회원가입"
을 하거나 혹은 서비스 접속을 위해 "로그인"
을 하는 행위가 "인증"의 예시이다.
여기서 인증은 이후 나오는 인가 방식 중 "세션"
,"쿠키"
,"토큰"
을 통한 "인증" 과는 다르다.
회원가입
사용자는 서비스의 가입을 위해 본인의 정보를 제공하고 서버는 다양한 사용자 확인 절차(이메일, 전화번호, SNS)를 이용해 확인 후 사용자 정보를 데이터베이스에 저장한다.
로그인
사용자의 회원가입 정보와 일치하는 일부 정보를 전달하여 가입한 사용자임을 증명한다.
◼ 인가란 ?
서비스 내에서 사용자에 대해 이용 권한을 허락하는 것이다. 예를 들면 접속한 사용자가 장바구니 페이지로 접근할 때 사용자의 "접근 권한을 허용"
하는 프로세스가 예시이다.
인가를 구현하는 방식은 크게 5가지 정도가 있다.
다섯가지 방법들을 언급하기에 앞서서 HTTP의 무상태성(Stateless) 특성을 알고 있으면 좋다.
📌 HTTP의 무상태성이란 무엇인가?
Stateless
란 서버가 클라이언트의 상태를 저장하지 않는 것을 말한다.
⬜ 상황 예시) 가게에서 물건 구매 : Stateful
고객: 이 바나나 얼마인가요?
점원 : 4000원입니다.
고객 : 2개 구매할게요.
점원 : 8000원 입니다. 신용카드, 현금 중에 어떤걸로 결제하시겠어요?
(구매 상품과 수량에 대한 state 저장/유지)
고객 : 신용카드로 하겠습니다.
점원 : 8000원 결제 완료되었습니다.
(구매 상품, 수량, 결제 수단에 대한 state 저장/유지)
이전 요청의 정보가 다음 요청에도 반영되어져 있다!
⬜ 상황 예시) 가게에서 물건 구매 : Stateless
고객 : 이 바나나 얼마인가요?
점원 : 4000원 입니다.
고객 : 바나나 2개 구매할게요.
점원 : 바나나 2개는 8000원입니다. 신용카드, 현금 중에 어떤걸로 결제하시겠어요?
고객 : 바나나 2개를 신용카드로 결제하겠습니다.
점원 : 8000원 결제 완료되었습니다.
각 요청의 정보만을 적용해 답변을 받고있다.
📌 HTTP의 무상태성을 유지하는 이유?
서버의 확장성
여러대의 서버를 운영하는 환경에서 각 서버의 정보를 공유한다는 것은 큰 리소스를 요구하기 때문에 HTTP는 Stateless 상태를 유지해야 비용을 절약할 수 있다.
📌 Stateless 특성을 로그인에 적용?
하지만 로그인을 한번 진행 한 뒤 서비스를 사용할 때 마다 로그인을 진행한다면 굉장히 불편할 것이다. 즉, 로그인을 통한 인증이 이뤄진 뒤에는 사용자의 접속 정보를 유지해야한다. 이를 위해 사용하는 방식이 앞서 언급한 다섯가지 방식이더.
다섯가지 인가 방식에 대해 알아보자
◼ Request Header를 이용한 방식 : HTTP Basic Authentication
가장 기본적이고 단순한 형태의 인증 방식으로 사용자 ID와 Password를 HTTP Request 에 Base64 Encoding 형태로 넣어서 인증을 요청하는 방식이다. 인코딩은 브라우저에서 진행하며 서버에서는 이를 디코딩한 뒤 DB에 접근하여 확인하고 OK로 응답하는 프로세스로 진행된다.
하지만 HTTP 기본 인증 방식은 로그인한 상태에서 매 요청마다 정보를 담아서 인증을 진행해야한다는 문제가 있다. 또,
HTTP 기본 인증(Basic Authentication) 방식은 사용자의 아이디와 비밀번호를 요청 헤더에 포함시켜 보내기 때문에, 요청을 가로채면 아이디와 비밀번호를 쉽게 볼 수 있다. 또한, 요청 헤더를 수정하는 공격(Header Injection)도 가능하다.
HTTPS와 함께 사용하면 그나마 보완할 수 있다. HTTPS를 사용하면, 클라이언트와 서버 간 통신이 암호화되기 때문에, 중간에 요청을 가로채더라도 아이디와 비밀번호를 알아내기가 어려워진다. 또한, HTTPS를 사용하면 요청 헤더를 수정하는 공격도 방지할 수 있다. HTTPS는 요청 헤더와 응답 헤더를 모두 암호화하기 때문에, 요청이나 응답을 가로채더라도 헤더를 수정하는 것이 불가능하다.
◼ Browser를 이용한 방식 : Cookie
브라우저 로컬 스토리지에 저장하며, Key-Value로 구성 String으로 이루어진 데이터 파일이다.개당 최대 4KB의 크기로 저장되며, 클라이언트에 300개까지 쿠키 저장이 가능하다.
쿠키의 구성 요소
쿠키의 동작 방식
set-Cookie
로 함께 전달Cookie: key=value
로 쿠키를 함께 보낸다.쿠키를 통해 여러 페이지를 이동하거나 서버에 새로운 요청을 할 때마다 매번 로그인(인증)을 하지 않고 유저 정보를 유지할 수 있다.
하지만 쿠키 방식은 쿠키만 탈취되면 유저 정보를 쉽게 빼낼 수 있다.
◼ Server를 이용한 방식 : Session
상대적으로 보안적인 부분에서 취약한 클라이언트를 보안하기 위해 Seesion을 활용해 서버의 도움을 받는다.
Session이란?
Session 동작 방식
유저는 아이디(이메일)과 비밀번호 등 필요한 로그인 정보를 HTTP 요청을 통해서 서버에 제출합니다.
서버는 이 정보를 받아 일반적으로 DB 에 담겨져 있는 유저의 로그인 정보와 대조하고,정보가 일치한다면 세션 정보를 x 에다 저장하고 유저의 요청에 대해서 Session ID 로 응답합니다.
이 session ID 를 클라이언트(유저)의 브라우저가 Key, Value 쌍을 저장할 수 있는 공간인 쿠키에 저장합니다.
쿠키에 저장 된 Session ID 는 유저가 이후에 동일한 웹사이트에서 새로운 요청을 할 때, 즉 유저 정보를 관리하거나 회원만 볼 수 있는 부분을 열람하고자 할때 서버에 보내게 됩니다.
Session 방식의 문제점 : Session DB
유저 정보를 클라이언트가 아닌 서버에서 관리하기에 보안 측면에서 뛰어나지만, 사용자가 많아지면 서버를 차지하여 성능을 낮출 수 있다.또한 Session 방식의 경우 모바일에는 적용할 수 없다는 문제점이 있습니다.
◼ Token을 이용한 방식 : JWT
Seesion 방식에서 Session 정보를 DB에 두어 관리하는 방식의 문제를 해결하기 위해 토큰 방식이 등장했다.
JWT 인증 방식이란란?
Json Web Token JSON 데이터의 소유자를 인증하는 방식.
JWT를 사용하는 인증 방식의 경우 두개의 토큰(AccessToken
, RefreshToken
)을 사용해 인증을 진행한다.
📌 Json Web Token
JWT는.
을 기준으로 3개의 부분으로 나눌 수 있다.
📌 AccessToken
클라이언트가 가지고 있는 실질적인 사용자의 자격 증명 정보가 담긴 토큰으로 보호된 정보들에 접근할 수 있는 권한부여에 사용한다. 클라이언트에서 요청이 오면 서버는 해당 토큰에 있는 정보를 활용하여 사용자 정보에 맞게 응답을 진행한다.
📌 RefreshToken
새로운 AcessToken을 발급하기 위해 사용하는 토큰으로 관리하는 방식이 다양하다.
JWT 전체 동작 과정
📌 토큰 발급 프로세스
- 사용자 로그인 : With Id,Password)
- 사용자 검증 : DB와 대조
- AcessToken. RefreshToken 발급
- RefeshToken 저장
- 두 토큰을 클라이언트에게 응답
- 두 토큰을 저장하고 API요청에는 AccessToken을 사용
📌 토큰 재발급 프로세스
- AccessToken을 이용해 API요청
- 토큰 검증 :
위치
,만료 여부
,토큰의 정보
- 토큰 만료 발생
- 토큰 만료를 클라이언트에 응답
- 클라이언트는 AccessToken + RefreshToken을 담아 토큰 재발급 요청
- 전달된 RefreshToken과 RefreshToken DB를 비교
- 새로운 AccessToken, RefreshToken 발급
- 클라이언트에 새로 발급된 두 토큰을 응답
📃 토큰 운영/관리 방법
탈취 가능성
AccessToken이 탈취 당한 경우
필연적으로 외부에 노출이되는 AccessToken의 경우 만료기간을 짧게 토큰 재발급에만 사용하는 RefeshToken 만료기간은 상대적으로 길게 한다. 탈취 자체를 막는다기 보다는 탈취되었을 경우 최소한의 정보만 유출한다.
RefreshToken이 탈취 당한 경우
AccessToken,RefreshToken이 탈취 당한 경우
만료 기간이 긴 RefreshToken이 탈취된 경우 공격자는 새로운 토큰을 발급 해서 사용할 수 있다. 이를 막는 방법은 refresh token Rotation
을 적용한다.
refresh token rotation 은 사용자가 refresh Token을 사용해 새로운 access token을 발급할 때 refresh token을 함께 새로 발급하여 저장하는 것이다. 이를 통해 공격자가 탈취한 refresh token과 저장한 refresh token이 일치하지 않아 정보 탈취를 막을 수 있다.
하지만...... 위 방식이 완전하기 위해서는 반드시 공격자보다 사용자가 먼저 refresh token을 통해 토큰 재발급 과정을 거쳐야한다. 만약 공격자가 먼저 토큰 재발급을 수행하면 오히려 사용자의 refresh token 정보와 저장된 토큰이 일치하지 않아 문제가 발생된다.
해결방법은 Redis의 저장형식을 [ key
: value
= userId
: refreshToken
]로 변경하여 해결한다.
예를들어 이미 공격자에 의해 새로운 refresh token이 업데이트 된 경우 사용자가 토큰 재발급 요청을 하면 사용자가 요청한 refresh token 으로 부터 userId를 사용해 Redis를 조회한다. 조회 결과로 나온 refresh token과 요청 refresh token이 일치하지 않는다면 재로그인 요청을 통해 access token, refresh token 모두 업데이트 절차를 거친다.
토큰 저장 위치
검증 프로세스 주최
클라이언트 + 서버
Only 서버
◼ OAuth를 이용한 방식
출처
https://velog.io/@iamtaehoon/sagah
https://velog.io/@hiy7030/Spring-Security-JWT-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90