JWT 웨 쓰는거에요?

김운채·2024년 3월 21일
0

Spring

목록 보기
8/10
post-thumbnail

보통 서버가 클라이언트 인증을 확인하는 방식은 쿠키, 세션, 토큰 3가지 방식이 있다.

기본적으로 HTTP프로토콜 환경은 connectionless, stateless한 특성을 가지기 때문에 서버는 클라이언트가 누구인지 확인해야한다.

connectionless
: 클라이언트가 요청을 한 후 응답을 받으면 그 연결을 끊어 버리는 특징

HTTP는 먼저 클라이언트가 request를 서버에 보내면, 서버는 클라이언트에게 요청에 맞는 response를 보내고 접속을 끊는 특성이 있다.

헤더에 keep-alive라는 값을 줘서 커넥션을 재활용하는데 HTTP1.1에서는 이것이 디폴트다.

HTTP가 tcp위에서 구현되었기 때문에 (tcp는 연결지향,udp는 비연결지향) 네트워크 관점에서 keep-alive는 옵션으로 connectionless의 연결비용을 줄이는 것을 장점으로 비연결지향이라 한다.

stateless
: 통신이 끝나면 상태를 유지하지 않는 특징

연결을 끊는 순간 클라이언트와 서버의 통신이 끝나며 클라이언트 정보는 유지하지 않는 특성이 있다.

인증은 두 가지 특징을 해결하기 위해 사용한다.

예를 들어, 쇼핑몰에서 옷을 구매하려고 로그인을 했음에도, 인증정보가 없다면 페이지를 이동할 때 마다 계속 로그인을 해야한다.

한 번 로그인을 하고, 그 사용자에 대한 인증을 유지하기 위해 위와 같은 인증 방식을 사용해야하는 것이다.


오늘 주인공은 토큰 방식 중 하나인 JWT 이지만 왜 토큰 인증 방식을 사용하는지 이해해보기 위해서 쿠키랑 세션에 대해서 찍먹해보자.

Cookie

쿠키는 Key-Value 형식의 문자열 데이터파일이다.

클라이언트가 웹사이트를 방문했을때, 그 사이트의 서버를 통해 클라이언트의 로컬 브라우저에 설치되는 작은 기록 정보 파일이다. 여기엔 각 사용자마다의 브라우저에 정보를 저장하니 사용자의 고유 정보 식별이 가능하게 된다.

동작방식

  1. 클라이언트가 페이지를 요청한다.
  2. 서버에서 쿠키를 생성해서 header에 쿠키를 포함시켜서 전달한다.
  3. 클라이언트가 요청을 보낼때마다 저장된 쿠키를 header 에 담아 보낸다.
    서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별하거나 정보를 바탕으로 추천 광고를 띄우거나 함.
    로그인할때 아이디랑 비밀번호 저장할래? 물어보는 것도 쿠키를 사용하는 것이다.

Cookie의 단점

  • 가장 큰 단점은 보안이다.
    클라이언트는 쿠키의 값을 그대로 보내기 때문에 정보가 노출될 위험이 있다.
  • 쿠키는 용량제한이 있어서 많은 정보를 담을 수가 없다.
  • 웹 브라우저마다 쿠키 지원형태가 달라서 브라우저간 공유가 불가능하다.
  • 쿠키의 사이즈가 커질수록 네트워크에 부하가 심해진다.

Session

쿠키의 보안이슈를 해결하고자, 세션은 클라이언트의 민감한 정보를 브라우저가 아닌 서버쪽에 저장하고 관리한다.

세션 객체는 Key에 해당하는 SESSION ID와 이에 대응하는 Value로 구성되어 있다.Value에는 세션 생성 시간, 마지막 접근 시간 및 User가 저장한 속성 등 이 Map 형태로 저장된다.

동작방식

Session의 단점

  • 쿠키를 포함한 요청이 외부에 노출되더라도 세션 ID 자체는 민감한 개인정보를 담고 있지 않는다.
    그러나 해커가 내 세션 ID 자체를 탈취하여 나인척 위장할 수 있어버림. (이는 서버에서 IP특정을 통해 해결 할 수 있긴 하다)

  • 서버에서 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해진다.

  • 각 서버는 세션 저장소를 공유하지 않는다. 이는 분산된 서버 환경에서는 인증 정보가 유지되지 못해서 새로 인증을 해야한다.


Token 인증

토큰 인증 기반 방식은 클라이언트한테 님 인증되었다는 의미로 토큰을 부여한다.

이 토큰은 유일하며 클라이언트는 토큰을 발급받고 나서는 또 서버에 요청을 보낼때 header 에 토큰을 담아서 보낸다.
그럼 서버가 이 토큰을 읽어서 자기가 보낸게 맞는지 어디서 엉뚱한걸 들고온건 아닌지 확인하여 인증 과정을 거친다.

위에서 세션은 서버에 세션정보를 가지고 있어야하고, 이걸 조회하는 과정이 필요하기 때문에 오버헤드가 발생한다.
하지만 토큰은 클라이언트쪽에 저장되어 메모리나 스토리지의 관련한 서버의 부담을 줄일 수 있다. 토큰 자체에 데이터가 있으니 이거만 읽으면되니까 ㅇㅇ

동작방식

토큰에는 요청자의 정보가 담겨있기에 서버는 DB를 조회하지 않고도 요청자의 정보를 알 수 있다.

Token의 단점

  • 쿠키/세션과 다르게 토큰 자체의 데이터 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해질수 있다.
  • Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.
  • 토큰을 탈취당하면 대처하기가 어렵다. => 사용 시간을 설정하는 방식으로 극복할 수 있음

JWT(Json Web Token)

JWT(JSON Web Token)란 인증에 필요한 정보들을 암호화시킨 JSON 토큰을 의미한다.

JWT 토큰을 header에 담아서 서버가 클라이언트를 식별한다.
Json 데이터를 Base64 URL-safe Encode 를 통해 인코딩하여 직렬화한 것이며, 토큰 내부에는 위변조 방지를 위해 개인키를 통한 전자서명도 들어있다.

그냥 다음 구조 사진을 보면 이해가 쉬움

JWT 구조

JWT는 . 을 기준으로 좌측부터 Header, Payload, Signature를 의미한다.

  • header : JWT 에서 사용할 타입해시 알고리즘의 종류가 담겨있다.

  • payload : 서버에서 첨부한 사용자 권한 정보와 데이터가 담겨있다.

  • signature : header 랑 payload를 합친 후에 Base64 URL-safe Encode 를 한 이후, header의 해시함수를 적용하고, private key로 서명한 전자서명이다.

Header와 Payload는 단순히 인코딩된 값이기 때문에 제3자가 복호화 및 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없다. 따라서 Signature는 토큰의 위변조 여부를 확인하는데 사용된다.

JWT 인증과정

  1. 사용자가 서버에 로그인 인증을 요청한다.

  2. 서버에서 클라이언트로부터 인증 요청을 받으면, Header, PayLoad, Signature를 정의한다.Hedaer, PayLoad, Signature를 각각 Base64로 한 번 더 암호화하여 JWT를 생성하고 이를 쿠키에 담아 클라이언트에게 발급한다.

  3. 클라이언트는 서버로부터 받은 JWT를 로컬 스토리지에 저장한다. (쿠키나 다른 곳에 저장할 수도 있음) API를 서버에 요청할때 Authorization header에 Access Token을 담아서 보낸다.

  4. 서버가 할 일은 클라이언트가 Header에 담아서 보낸 JWT가 내 서버에서 발행한 토큰인지 일치 여부를 확인하여 일치한다면 인증을 통과시켜주고 아니라면 통과시키지 않으면 된다. 인증이 통과되었으므로 페이로드에 들어있는 유저의 정보들을 select해서 클라이언트에 돌려준다.

  5. 클라이언트가 서버에 요청을 했는데, 만일 액세스 토큰의 시간이 만료되면 클라이언트는 리프래시 토큰을 이용해서

  6. 서버로부터 새로운 엑세스 토큰을 발급 받는다.

JWT 토큰을 사용하는 이유

1. 신뢰성

JWT의 목적은 정보 보호가 아닌 위조 방지에 있다.

서버는 JWT를 이용해서 토큰의 정보를 아는게 중요한게 아니라 이 토큰이 유효한 토큰인지 검사하는 것이 중요하다.

위 글에서 Header와 Payload는 제 3자가 조작할 수 있지만 Signature 을 통해서 이 토큰이 위변조된 토큰이 알 수 있다고 했다.

내 토큰이 A+B+C 라고 해보자. 다른 유저가 내 토큰의 B를 수정해서 A+B'+C로 서버에 요청을 보내면 서버는 유효성 검사를 시행한다.
서버에서 이 토큰을 검증후 생성한 JWT가 A+B'+C' 라면 signature가 불일치로 판명났으니 토큰이 조작된 토큰임을 알 수 있는 것이다.

이와 같은 이유로 토큰 인증 방식이 신뢰성을 가진다.

2. 서버의 Stateless

ㄱ나니? 위에서 서버는 Stateless한 특징을 가지고 있다고 했다.

Stateless : 클라이언트와 서버의 통신이 끝나며 클라이언트 정보는 유지하지 않는 특성

정의만 보면 이전 사용자의 정보를 저장하지 않아서 계속 추가적인 데이터 접속이 필요할테데 왜 Stateless 통신을 가지고가는걸까?

Stateless는 서버가 클라이언트에 대한 정보를 저장하지 않기 때문에, 서버와 클라이언트가 느슨하게 결합된다. 이는 서버의 설계를 간단하게 한다는 장점이 있다.

또한, 어떤 서버에 요청해도 동일하게 요청을 처리한다는 특징을 가진다. 이 말은, 분선서버 환경에서 어떤 서버에 요청해도 동일한 처리가 진행된다는 말이다.

이와같은 이점은 곧 서버 규모 확장/축소가 쉬워진다는 scale-out 이점으로 작용한다.

JWT는 저장소를 갔다오지 않으니 서버의 Stateless한 특징을 살린다. 이는 특히 세션 방식에 비해서 분산 환경에서 유용하게 쓸 수 있다는 장점이 있다.

정리하자면 JWT 의 장점은
  • Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있다.

  • 세션과 같이 별도의 저장소가 필요없다.

  • 별도의 DB 조회가 필요없다. payload에 유저이름과 유저권한을 같이 두어 보내면 서버는 별다른 DB 조회 필요없이 원하는 정보를 얻을 수 있음.

  • 쿠키와 다르게 다른 브라우저와 공유가 가능하다.

  • 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버가 무상태(StateLess)가 되어 서버 확장성이 우수해짐.

  • 모바일에서도 잘 동작한다. (세션은 모바일불가능)

JWT 는 완벽한가?

역시 ㅅ ㅏ람이 만든건 한계가 있음

1. payload의 보안

위에서 Header랑 Payload는 단순히 인코딩된 값이기 때문에 제3자가 복호화 및 조작할 수 있다고 했다. 그래서 디코딩을 통해서 해당 내용을 읽을 수 있다. 이는 민감한 정보를 payload에 저장하면 다~ 볼 수 있다는 뜻. 노출되면 안되는 정보는 담지 않도록 주의해야한다.

2. 토큰 탈취

또한 아까 언급했다싶이 토큰 탈취의 위험성이 있다.

JWT로 인증을 진행하면 아무 값도 매핑하지 않기 때문에 저장소에서 데이터를 찾지 않는다. 다시 말해, 토큰이 탈취되면 서버는 아무런 값을 매핑하고 있지 않으므로 만료되기 전까지 아무런 대응을 할 수 없다.

이 부분을 대비하기 위해 토큰의 유효시간을 부여하는 방식으로 대응할 수 있지만, 유효시간이 짧을 경우에 사용자는 로그인을 자주 해야하는 번거로움이 생길 수 있다.

이런 문제를 해결하기 위해 Access Token을 그대로 사용하는 것 보다는 Refresh Token이라는 추가적인 토큰을 활용해서 이중 보안으로 인증하는 방식을 취한다.

  • Access Token : 클라이언트가 갖고있는 실제로 유저의 정보가 담긴 토큰으로, 클라이언트에서 요청이 오면 서버에서 해당 토큰에 있는 정보를 활용하여 사용자 정보에 맞게 응답을 진행
  • Refresh Token : 새로운 Access Token을 발급해주기 위해 사용하는 토큰으로 짧은 수명을 가지는 Access Token에게 새로운 토큰을 발급해주기 위해 사용. 여기서 Refresh Token은 서버 저장소에 저장한다.

쉽게 말해, Access Token은 실제 서비스 내 인증에 사용되는 토큰, Refresh Token은 Access Token 재발급을 위한 토큰으로 이해하자.

Refresh Token

각 서비스 별로 정책이 다르지만 Access Token은 짧은 유효 기간을 가진다. 탈취 당하더라도 금방 만료되어 악용할 여지를 최대한 줄이는 것이다.

이에 반해 Refresh Token은 비교적 긴 시간의 토큰을 생성한다. 여기서 Refresh Token만 서버에 저장한다.
이 Refresh Token은 블랙리스트를 작성하거나 토큰과 정보의 매핑을 삭제 및 수정하는 등으로 관리된다.

🤷‍♂️ 아니 그럼 Refresh Token이랑 세션이랑 뭐가 다른데... DB 저장 안하려고 JWT 쓴다며!!! 라고 생각했다면 침착하고 읽어보자

일단 세션은 인증이 필요한 요청 마다 저장소를 접근하는데, Refresh Token은 Access Token이 만료될때까지 저장소에 접근하지 않는다.
그러니까, Access Token 만료 시간만큼 저장소 접근 횟수 차이가 있는 것이다.

RTR (Refresh Token Rotation)

그럼 이 Refresh Token의 탈취에 대해서는 어떻게 방지할까?
이에 대한 방법으로 RTR을 사용할 수 있다.

RTR은 단순하게 말해서, Access Token 재발급 시 Refresh Token을 재발급함으로 Refresh Token을 1회성으로 만드는 방법이다.

1회성으로 만들면서 Refresh Token이 가지고 있는 긴 유효 기간에 대한 문제를 해결한다. 뿐만 아니라 이전에 발급한 Refresh Token을 활용한 Access Token 발급 요청이 다시 들어오면, 해당 Refresh Token의 탈취 여부를 파악할 수 있다.

예를 들어서 내가 Refresh Token을 사용하고 있는데 해커가 이전 Refresh Token으로 재발급 요청을 보내면 뭐여 이거 탈취된건가? 하고 탈취가 있었음을 확인할 수 있는 것이다.

하지만 결국 "탈취가 되었어야" 공격이 있었음을 알 수 있다는 점에서는 문제가 있다.


어쩐지 너무 길어진 글이다.

어쨌든, 이렇게 보안과 성능의 관계성에 대해서 생각하고 서비스의 성격에 따라 빠른 처리를 요구하는 서비스인지, 강한 보안을 요구하는 서비스인지 파악하고, 어떤 기술을 선택해야할지 대해서는 합리적인 이유가 있어야한다.

0개의 댓글