# [network] JWT

## 왜 필요한데?

- 보안 문제
    
    만약 서버와 클라이언트가 서로 유저 정보를 순수 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(메타 데이터)
    - 여기에 쓰인 signature 알고리즘 이름
    - 토큰의 타입(대부분 JWT)
- Payload
    - 실제 담긴 데이터
- Signature
    - 암호화 알고리즘으로 생성된 문자열
    - 토큰의 **유효성을 검증**하는데 쓰인다.

![](https://supertokens.com/static/b0172cabbcd583dd4ed222bdb83fc51a/40601/jwt-structure.png)

## JWT 흐름

### 1. Payload 생성 (로그인시 서버쪽에서 발급 )

유효기간을 제외한 나머지 데이터들은 선택 사항

```json
{
    "userId": "abcd1234",
		"userName": "wonseok2877",
    "expiry": 1646635611301
}
```

### 2. signing key 생성 & 해싱 알고리즘 선택

- Signing key
- Signing algorithm

보통 이 정보는 서버에서만 비밀스레 간직하고 있는다.

### 3. Header (메타 데이터) 생성

JWT 자체를 설명하는, 즉 메타 데이터를 Header로써 넣는다. 

토큰의 타입(`typ`)과 해싱에 쓰인 알고리즘 이름(`alg`)을 넣는다.

```json
{
    "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 부분을 디코딩 →
    
    ```json
    {
    		"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)의 **재발급만을 위한** 토큰이다. 귀찮음을 방지하기 위한 토큰인만큼 유효기간이 길다.

## 흐름

![](https://github.com/wonseok2877/CS-study/raw/master/network/images/ac_re_token.png)

1. 사용자가 로그인 성공 
→ 서버는 JWT 토큰 (Access Token & Refresh Token)을 클라이언트에게 발급 
→ 데이터베이스에 Refresh Token을 저장
2. 클라이언트측은 가장 안전한 곳에 두 토큰을 저장해놓고, (ios는 쿠키파) 
요청이 있을 때마다 이 ac token을 HTTP request **헤더**에 담아 보낸다.
3. 만약 클라이언트측에서 만료된 access token을 보냈을 경우, 
서버는 같이 보내져온 Refresh Token을 보고 DB에 저장된 Refresh Token과 비교 
→ 일치하면 다시 Access Token을 발급 
4. 사용자가 로그아웃 
→ 서버는 DB의 Refresh Token을 삭제 (더 이상 두 토큰 모두 사용 불가) 

> refresh token이 탈취되면 어떻게 할건데?
> 
> → 답 없음. 서버측에서 어떻게든 그 사실을 알아내서 DB상의 refresh token을 삭제해야 함
> 

## 장단점 정리

### 장점

- `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://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-Stateful-Stateless-%EC%A0%95%EB%A6%AC)

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](https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-Access-Token-Refresh-Token-%EC%9B%90%EB%A6%AC-feat-JWT)
