Yet Never Lose Faith

- Good to Great , Jim Collins

How To Preprocess Image Data 자세히보기

Java

JWT 뿌시기 (세션방식과 비교/정의/구조/검증방법/장단점) (feat.Refresh 토큰)

Kellyyyy 2022. 11. 6. 18:59

1. 인증의 필요성


클라이언트는 서버 사이에는 다양한 요청과 응답을 발생한다. 예를 들어 쇼핑몰 사이트에서는 상품 리스트 조회나 주문 처리 등이 있을 것이다.

만약, 서버가 인증 과정 없이 클라이언트의 요청을 처리한다면 어떻게 될까?

인증되지 않은 클라이언트(ex. 해커)는 마음대로 상품 정보를 수정할 수도 있고, 주문을 넣을 수도 있다. 그래서 서버는 로그인 절차를 통해서 인증된 클라이언트에게만 '통행권'을 부여한다. 클라이언트는 이후 요청을 보낼 때 서버에 그 통행권을 제시하고, 서버는 통행권을 체크해서 유효한 경우에만 적절한 응답을 주는 방식으로 보안성을 높인다.

2. 인증의 두가지 방식(세션 vs JWT)

서버가 인증을 처리하는 방식에는 2가지가 있다. 바로 세션과 JWT 방식인데, 이 2 가지 방법은 서버가 발행한 통행권의 종류로 구분된다. 세션은 세션키 값이고, JWT는 JSON 형식의 토큰값이 통행권이다. 이 포스팅에서는 JWT가 무엇이고, 어떻게 통행권의 역할을 하는지 다룰 예정이다.

그전에, 우선 세션에 대해 간단하게 살펴보자.

세션

통행권 발급


① 사용자가 로그인을 했을 때 서버는 통행권으로써 세션키를 발급한다.
② 서버 내부적으로 사용자 ID와 세션키를 매핑하여 저장해둔다.
③ 클라이언트에게 세션키를 전달한다.

통행권 체크


① 클라이언트는 요청시에 해당 세션키가 적힌 통행권을 제시한다.
② 서버는 세션키가 유효한지, 세션을 발급했던 사용자가 맞는지 등을 체크한다.
③ 세션키가 유효할 경우 응답을 제공한다.

세션 방식의 한계점

서버는 자신이 발급한 세션 키 정보만 가지고 있기 때문에 서버가 여러 개인 경우는 세션키를 공유할 수 있도록 데이터베이스에 저장하는 방식이나 메모리서버를 새로 만드는 작업이 필요하다.

JWT는 세션의 이러한 한계점을 보완해줄 수 있는 특징을 가지고 있는데, JWT 자체로 유효성 판단이 가능하기 때문에 통행권이 유효한 것인지 확인하기 위해 데이터베이스나 메모리 서버에 기록하거나 조회할 필요가 없다. 좀 더 자세하게 알아보자.


3. JWT란?

https://jwt.io/introduction는 JWT 라이브러리를 제공하는 사이트이다. JWT의 개념에 대해 자세하게 설명되어있고, 직접 만들어볼 수 있는 기능도 제공한다. 이 페이지에 나온 설명을 따라서 알아가보겠다.

JWT는 RFC 7519라는 open standard라고 한다. RFC란 Request for Comments의 약어로, 인터넷 기술에 적용가능한 새로운 연구, 혁신 기법등을 아우르는 메모이다. RFC 편집자는 RFC 문서를 발행할 때마다 일련번호를 부여한다. 쉽게 말하면 어떤 똑똑한 사람들이 발견한 7519번째 혁신기법이라고 보면 되겠다.


JWT는 전자적으로 서명되기 때문에 믿을만하고, 아래 2가지 방식으로 서명될 수 있다.

① 시크릿 키를 가지고 서명하는 방식 (HMAC 알고리즘)
② 공개/개인키를 가지고 서명하는 방식 (RSA or ECDSA)

여기서 중요한 것이 바로 서명이다. 서명은 우리가 계약서에 하는 것처럼 '내가 확인했다' 것을 의미한다. JWT는 이 서명이 담긴 정보이고, 서명을 한 주체는 서버이다. 로그인을 한 사용자에게 자신이 한 서명이 담긴 토큰을 부여하고, 이후 요청시에 이 토큰에 자신이 한 서명이 있는지 체크해서 유효성을 판단한다.

4. JWT의 구조

JWT는 아래 3개로 이루어져 있다.

① header
② payload
③ signature

① Header

Header는 일반적으로 ① 토큰의 타입과 ② 알고리즘 정보 2가지로 구성되어있다. 토큰의 타입은 JWT이고, 알고리즘은 HMAC SHA256(HS256)이나 RSA와 같이 서명을 한 방식을 지정한다.

{
  "alg": "HS256",
  "typ": "JWT"
}

헤더는 Base64Url 방식으로 암호화된다.

② Payload

Payload는 claims를 담고 있다. claims은 쉽게 말하면 내가 싣고 싶은 정보이다. 사용자에 대한 정보일 수 있고, 추가적인 다른 정보일 수 있다. 토큰을 발행한 시간, 만료되는 시간, 사용자 id, 권한 등급 등이 있을 수 있다.

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Payload 역시 Base64Url 방식으로 암호화된다.

※ 주의사항

헤더와 페이로드는 Base64Url방식으로 암호화되기 때문에 누구에게 읽힐 수 있는 정보이다. '서명'된 토큰은 이 정보가 변조되는 것은 막아주지만 누구에게나 읽힐 수 있기 때문에 비밀 정보를 헤더나 페이로드에 담지 말 것을 당부하고 있다.

즉, JWT는 정보를 암호화해서 보내는 것이 목적이 아니라 인증의 목적이다. 사실 정보를 암호화해서 보내려면 굳이 토큰을 만들 필요 없이 그 정보만 암호화해서 보내면 된다.

③ Signature

헤더와 페이로드 값을 특정 알고리즘으로 서명하여 Signature를 생성한다. 문서에서 언급한 서명하는 방식은 크게 2가지이다.

  • 비밀키를 가지고 서명하는 방식 (HMAC 알고리즘)
  • 공개/개인키를 가지고 서명하는 방식 (RSA or ECDSA)


서명방식을 간략하게 짚고 넘어가보자.

(1) 비밀키를 가지고 서명하는 방식 (HMAC 알고리즘)

  • 서버가 가진 비밀키로 헤더와 페이로드를 암호화한다.
  • Hash 알고리즘이기 때문에 한번 암호화되면 복호화할 수 없다.
  • 인증을 하기 위해서는 비밀키 값을 알아야하기 때문에 상대방에게 키값을 전달해줘야한다.

(2) 공개/개인키를 가지고 서명하는 방식 (RSA or ECDSA)

  • 공개/개인키 쌍을 이용한 방식이다.
  • 비밀키 방식과는 다르게 내가 가진 키를 상대방에게 전달해줄 필요가 없다. (보안성 ↑)


이 방식을 이해하기 위해서는 공개키/개인키 방식의 개념을 알고 있어야한다. 우선, 공개키는 블로그 같은 곳에 공개해도 되는 키값이고, 개인키는 서버 자신만 알고 있어야하는 키이다. 그래서 서버는 공개키/개인키 쌍을 갖고 있게 되는데, 공개키로 암호화를 하면 개인키로만 복호화할 수 있고 반대로 개인키로 잠그면 공개키로만 복호화할 수 있다.

이런 특성 때문에 공개키/개인키 방식은 암호화 또는 전자서명을 할 때 사용된다.

1. 암호화 - 정보를 받을 상대방의 공개키로 암호화
A가 B에게 암호화된 정보를 전달하고 싶으면 B의 공개키로 정보를 암호화하면 된다. B의 공개키로 암호화된 정보는 B의 개인키로만 복호화가 가능하기 때문에 B만 복호화가 가능하다.

2. 전자서명 - 서명하는 서버의 개인키로 암호화
A가 B에게 보낼 정보에 내가 정보를 만들었다는 것을 인증하는 용도로 서명을 하고 싶다면 A의 개인키로 암호화를 하면 된다. B는 A의 공개키로 정보를 복호화해보고, 제대로 복호화가 된다면 그것은 A만 암호화할 수 있다는 것을 의미하기 때문에 인증된다.

A가 B에게 정보를 보낼 때 A의 개인키로 잠근 후 B의 공개키로 한번 더 잠근다면 그 정보는 B만 해독할 수 있게 암호화되면서 A가 작성한 문서라는 것을 보장한다. (암호화 + 서명)

JWT는 인증의 목적이기 때문에 정보를 보내는 서버에서 자신의 개인키로 헤더와 페이로드를 암호화하여 시그니처를 생성한다.

④ 종합(header + payload + signature)

이렇게 생성된 시그니처는 헤더, 페이로드와 함께 '.'으로 연결되어서 아래와 같은 형태를 띈다.

5. 토큰을 검증하는 방법


클라이언트는 서버에게 받은 토큰을 일반적으로 Authorization header에 Bearer 타입으로 담아서 보낸다. 그러면 서버는 이 토큰을 각각 header와 payload, signature로 쪼갠다. header에는 서명 방식(HS256, RS256.. )이 담겨있을 것이기 때문에 해당 알고리즘 별로 토큰 체크작업을 해서 서명의 유효성을 판단한다.

(1) 비밀키를 가지고 서명하는 방식 (HMAC 알고리즘)

이 방식은 복호화가 불가능하기 때문에 토큰에 있는 헤더와 페이로드 값을 서버가 가진 비밀키로 암호화 한 후 토큰의 시크니처와 비교하는 방식으로 유효성을 판단한다.

(2) 공개/개인키를 가지고 서명하는 방식 (RSA or ECDSA)

토큰을 서버의 개인키로 암호화했기 때문에 공개키로 복호화하여 유효성을 판단한다.

JWT는 Stateless한 인증 메커니즘인데, Stateless란 서버가 클라이언트의 상태를 보장하지 않는다는 것을 의미한다. 이는 JWT 토큰 자체만으로 유효성 체크가 되기 때문에 서버가 내부적으로 토큰의 상태를 관리하지 않는다는 것을 의미한다.


6. JWT의 장단점 (feat. Refresh 토큰)

JWT의 장점

세션 방식은 모든 서버가 세션값을 공유하기 위해서는 DB에 저장하거나 메모리 서버를 구축해야한다고 했다. JWT는 토큰 자체만으로 유효성을 검증할 수 있으므로 확장성 측면에서 좋다.

JWT의 한계

반대로 서버가 토큰의 상태를 관리하지 않기 때문에 토큰을 누군가에게 도둑맞았을 경우에 대응이 어렵다. 훔친 토큰을 가지고 요청을 보내는 경우에 서버는 이 토큰값을 무효화시킬 수 있는 방법이 없다.


Refresh 토큰의 등장

일반적으로 훔친 토큰으로 인한 피해시간을 줄이기 위해 토큰의 유효시간을 매우 짧게 생성한다.(ex.30분) 하지만 토큰의 유효시간을 짧다는 것은 다시 말해 사용자가 새로운 토큰을 받기 위해 그만큼 자주 로그인을 해야한다는 것을 의미한다. 이를 방지하기 위해 로그인 시 유효시간을 길게 설정한 Refresh 토큰을 함께 발급하고(ex.일주일), 이 토큰은 사용자 정보와 매칭해서 DB에 저장해둔다.

클라이언트는 이후 요청시 인증토큰(이하 Access 토큰)과 Refresh 토큰을 같이 제시하고, 서버는 Access 토큰의 유효기간이 만료됐더라도 Refresh 토큰이 유효한 경우에는 사용자가 재로그인 없이 새로운 Access 토큰을 발급받을 수 있도록 한다. Refresh 토큰이 유효하지 않는 경우(Refresh 토큰 없이 요청을 보내거나/사용자 ID와 매칭된 리프레스 토큰이 없거나/리프레시 토큰의 유효시간이 경과한경우)에는 Refresh 토큰을 만료처리 해버리고, 다시 로그인을 할 수 있도록 안내한다.

Access 토큰이 유효할 때는 별도에 DB 작업이 필요없지만 유효하지 않을 때는 Refresh 토큰의 상태를 체크해야하면서 DB를 조회하거나 무효화처리하는 작업 등이 필요하기 때문에 DB I/O가 줄어드는 JWT의 장점이 약간 반감된다.

Refresh 토큰도 훔친다면?

이때는 방법이 없다. 사실 로그인 비밀번호를 훔치는 경우도 존재할 수 있다. 그래서 우리는 2-factor 인증처럼 이중, 삼중의 보안벽을 만들어 두는 것이다. (그런데 2-factor 인증번호까지 훔쳤다면? 이때는 진짜 방법이 없다.)벽을 하나 더 만드는 방법 중 하나는 새로운 Access 토큰을 발급받을 때 Refresh 토큰도 새로 발급하는 것이다. Refresh 토큰도 30분마다 새로 발급되기 때문에 해커가 정보를 빼내갈 수 있는 시간을 줄일 수 있다.

'Java' 카테고리의 다른 글

stream()  (0) 2021.04.24
정규식  (0) 2021.04.21