❓ 왜 필요한데?
- 보안 문제그래서 유저를 식별할 수 있는 중요한 데이터를 토큰화시켜 주고받게 된다.
- 만약 서버와 클라이언트가 서로 유저 정보를 순수 JSON으로 보내게 되면, 이게 유효한 정보인지 확인할 방법이 없다. 만약 악의를 가진 공격자가 유저 ID를 바꿔서 요청을 했을 경우, 서버에선 무슨 일이 일어난 건지 알 방법이 없다.
- HTTP의 특징매번 사용자가 로그인을 하기는 너무 불편하고, 그렇다고 브라우저 스토리지에 아이디와 비밀번호 (인증 정보)를 넣자니 너무 위험하기 때문에, ‘인증 되었음’을 나타내기 위한 정보인 세션과 토큰이 등장했다.
- 기본적으로 HTTP 통신은 무상태(Stateless)이다.
❓ 세션에 비해 장점이 뭐지?
: 세션 방식의 경우, 서버의 메모리에 저장된다는 것이 단점의 주 원인이다.
덜 stateful 하다
JWT 또한 완전히 stateless하다고 할 수 없다. refresh token이 DB에 저장되기 때문.
그러나 JWT 방식이 세션 방식보다 덜 stateful 하다. refresh token은 DB에 저장되므로 stateful하지만, 평소에 쓰는 access token은 stateless하기 때문이다.
확장성의 문제
❗근데… stateful인게 뭐가 문제지?
stateful의 문제점은 해당 서버가 멈추거나 여러 이유로 해당 서버가 못쓰게 되어 다른 서버를 사용해야 할때 발생한다.
새로운 서버에서는 이전 서버에서 가지고 있던 상태값들을 가지고 있지 않기 때문이다.(서비스가 커져서 서버 컴퓨터가 늘어난 상황을 상상해보자. 서버 A, 서버 B, …)
즉, 서버 A만 내 정보를 갖고 있기 때문에 중간에 서버 A에 장애가 생기면 클라이언트는 일을 처음부터 다시 해야된다.
또한 Stateful 방식은 하나의 서버가 1만 명의 클라이언트를 처리할 능력이 있을 때 그보다 많은 수의 클라이언트가 몰리면, 이미 연결된 1만 명의 클라이언트 중 일부가 빠져야 다음 클라이언트가 처리된다는 한계가 있다.
당연히 클라이언트 상태들을 들고 있으니 용량 한계가 존재하기 때문이다.
서비스의 규모가 커져서 서버를 여러대로 확장 및 분산해야 한다면 세션을 분산시키는 기술을 따로 설계해야 한다. 어떤 컴퓨터에는 있는 유저 정보가 다른 컴퓨터에는 없을 경우가 생기기 때문이다. 로그인을 했는데 웹사이트에서는 로그인을 다시 하라고 요구할 것!
그러나 이를 해결할 수 있는 방법이 있다.
저장 공간의 용량
세션은 서버의 메모리 내부에 저장이 된다. 유저가 수천명인 대형 서비스에서는 세션의 양이 많아지는 만큼 메모리에 부하가 걸릴 수 있다.
🔍 JWT 구조
JWT 방식에서는 토큰을 클라이언트에 저장하고, 요청시 HTTP 헤더에 토큰을 첨부해 보내 서버의 인가를 받는다.
- Header(메타 데이터)
- alg : 여기에 쓰인 signature 알고리즘 이름
- typ : 토큰의 타입(대부분 JWT)
- Payload
- 실제 담긴 데이터
- Signature
- 암호화 알고리즘으로 생성된 문자열
- 토큰의 유효성을 검증하는데 쓰인다.
🔍 JWT 흐름
1. Payload 생성 (로그인시 서버쪽에서 발급 )
유효기간을 제외한 나머지 데이터들은 선택 사항
{
"userId": "abcd1234",
"userName": "wonseok2877",
"expiry": 1646635611301
}
2. signing key 생성 & 해싱 알고리즘 선택
- Signing key
- Signing algorithm
보통 이 정보는 서버에서만 비밀스레 간직하고 있는다.
3. Header (메타 데이터) 생성
JWT 자체를 설명하는, 즉 메타 데이터를 Header로써 넣는다.
토큰의 타입(typ)과 해싱에 쓰인 알고리즘 이름(alg)을 넣는다.
{
"typ": "JWT",
"alg": "SHA256"
}
4. Signature 생성
- Payload를 인코딩 → eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ
- Header을 인코딩 → eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
- 둘을 합치기(.으로 구분) → eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ. There is no special reason to do it this way other than to set a convention that the industry can follow.
- 위의 문자열을 SHA256 (알고리즘)으로 암호화 → 인코딩 → 3Thp81rDFrKXr3WrY1MyMnNK8kKoZBX9lg-JwFznR-M (signature 탄생)
5. JWT 완성
header.payload.signature
6. JWT 검증 (클라이언트의 요청 Header에 담긴 JWT)
- Fetches the header part of the JWT (eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9).
- Header 부분을 디코딩 →
- { "typ":"JWT", "alg":"SHA256" }
- 애플리케이션에서 사용하는 JWT 생성 방법과 같은지 확인. typ 필드의 값이 JWT인지, alg 의 값이 SHA256인지. 만약 다르다면, 해당 요청(JWT)을 거절.
- signing secret key를 이용해 SHA256 알고리즘으로 풀고 → 디코딩 → Body 를 가져온다.
- 이렇게 만든 Header과 Body를 가지고 JWT 생성 때와 마찬가지로 signature을 생성
- 생성된 signature이 JWT의 signature과 같은지 확인. 만약 다르다면, 해당 요청을 거절
- 만약 expiry 시간보다 지나있다면 거절
Access Token & Refresh Token
위에서처럼 토큰을 하나만 사용한다면 그건 access token만 쓰는 방식이다. 그런데 문제가 생겼다.
보안 vs 귀찮음
만약 유효기간을 길게 만들어버리면, 제3자에게 탈취당했을 경우 보안적으로 취약해진다. 유효기간이 끝날 때까지 무슨 짓을 벌일지 모른다!
반대로 유효기간을 짧게 만들면, 유저가 그만큼 로그인을 자주 해서 새롭게 토큰을 발급받아야 하니 불편함이 이만저만이 아니다.
access token의 유효기간은 짧게 해서 보안을 챙기되, 귀찮지 않게 발급만을 위한 토큰을 만들면 어떨까?
여기서 refresh token의 존재 의의가 나온다. 말 그대로 refresh, 토큰(access token)의 재발급만을 위한 토큰이다. 귀찮음을 방지하기 위한 토큰인만큼 유효기간이 길다.
🔍 클라이언트-서버 통신 흐름
- 사용자가 로그인 성공 → 서버는 JWT 토큰 (Access Token & Refresh Token)을 클라이언트에게 발급 → 데이터베이스에 Refresh Token을 저장
- 클라이언트측은 가장 안전한 곳에 두 토큰을 저장해놓고, (ios는 쿠키파) 요청이 있을 때마다 이 ac token을 HTTP request 헤더에 담아 보낸다.
- 만약 클라이언트측에서 만료된 access token을 보냈을 경우, 서버는 같이 보내져온 Refresh Token을 보고 DB에 저장된 Refresh Token과 비교 → 일치하면 다시 Access Token을 발급
- 사용자가 로그아웃 → 서버는 DB의 Refresh Token을 삭제 (더 이상 두 토큰 모두 사용 불가)
refresh token이 탈취되면 어떻게 할건데?
→ 답 없음. 서버측에서 어떻게든 그 사실을 알아내서 DB상의 refresh token을 삭제해야 함
JWT 방식 장단점 정리
장점
- Header 와 Payload를 가지고 Signature 를 생성하므로 데이터 위변조를 막을 수 있다.
- access token : 세션과 다르게 서버나 DB에 저장되지 않아 stateless하다. refresh token : 서버 메모리가 아닌 DB에 저장된다. 명백히 stateful하지만, 서버 스케일업 DB
- 토큰에 대한 검증 및 전달할 정보를 자체적으로 지니고 있으며, 토큰을 기반으로 oauth 등의 다른 시스템에 접근 및 공유가 가능하다.
- (Base64 URL Safe하기 때문에 범용성이 높다.)
단점
- 토큰을 탈취당하면 대처하기 어렵다.
- Payload 자체는 암호화되지 않기 때문에 중요한 정보를 담을 수 없다.
- JWT의 토큰의 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해질 수 있다.
참고
https://inpa.tistory.com/entry/WEB-📚-Stateful-Stateless-정리
https://hudi.blog/refresh-token/
https://supertokens.com/blog/what-is-jwt#5-creating-the-jwt
https://codepedia.info/aspnet-core-jwt-refresh-token-authentication
https://inpa.tistory.com/entry/WEB-📚-Access-Token-Refresh-Token-원리-feat-JWT
'CS > 네트워크' 카테고리의 다른 글
[CS 네트워크] HTTP와 HTTPS (0) | 2023.04.12 |
---|