JWT를 이해하기 위한 기초 지식

byeol·2023년 4월 20일
0
post-thumbnail

Spring + OAuth2.0 + JWT를 이용해서 로그인을 구현해야 하는데
기초 없이 분석하다 보니 밑 빠진 독에 물붓기가 되는거 같습니다.

그래서 최주호님의 스프링 시큐리티 & JWT 강의를 듣게 되었고
머리 속에 돌아다니는 내용들을 글로 정리해보려고 합니다.


(내일 정리)
Authorization Code Grant Type(Naver)
스프링부트 시큐리티 JWT(토큰)
스프링부트 시큐리티 JWT + OAuth2.0(토큰)
Client Credentials Grant Type

네이버는 기본적인 provider가 아니기에
authorization-grant-type: authorization_code
rediret-url: http://localhost:8080/login/oauth2/code/naver
필요하다.
provider:

세션

어떤 흐름인지 살펴보자

세션은 쿠키 기반이기 때문에 세션 ID는 쿠키에 저장되며 이후 발급된 세션ID는 쿠키에 저장된 정보로써 함께 서버로 전달됩니다.

클라이언트가 요청을 보내면 서버는 쿠키를 생성하고 클라이언트에게 유일한 세션 ID를 발급합니다.

이 쿠키와 세션 ID를 응답에 보내고 이를 바탕으로 클라이언트는 쿠키를 생성하고 안에 세션 ID를 저장합니다.

후에 같은 요청을 보낼 때 해당 쿠키를 보내는데 그 쿠키에는 세션 ID가 포함되어 있다. 서버는 쿠키에 저장된 세션 ID를 확인하고 사용자 ID와 매핑된 서비스를 제공한다. 이후 업데이트 된 쿠키와 세션ID를 클라이언트에 보냅니다.

한 가지 생각해 볼 것이 있습니다.
바로 Client가 100만명이라고 생각해 보면
저 100만명의 세션 ID를 저장하고 있어야 합니다.
Sever 프로그램에 할당된 RAM의 크기는 한정되어 있기 때문에 이를 해결할 방법이 필요합니다.

  1. 로드밸런싱을 통해 한정된 크기의 서버를 여러개 설치한다.
    여기서 문제는 각 서버가 세션ID가 어떤 User와 연결되어 있는지 모두 알아야 한다는 것!
    그래서 모두가 같은 세션 정보를 가지고 있어야 하는데 이는 굳이 한대를 가지고 있는거와 다를바 없다.

  2. 모든 서버가 하나의 DB에 세션 ID를 저장해서 공유한다.
    사실 이 말은 하드디스크에 저장한다는 말입니다. DB는 하나의 파일이기 때문입니다.
    하드디스크에 저장한다고 생각해봅시다.
    하드디스크에 저장한다면 CPU가 세션 ID 하나를 찾기 위해
    CPU->RAM->HDD의 과정을 거치게 됩니다.
    이 과정에 시간적 비용이 많이 듭니다.

    하드디스크는 하나의 데이터를 찾기 위해 전체 탐색을 진행하기 때문입니다.

  3. DB에 저장하는 것이 아니라 메모리 공유 서버인 Redis를 이용하여 세션ID를 저장한다.
    중앙 세션 관리 시스템에서 하나의 문제가 시스템 전체로 확산된다는 문제점이 있다.

따라서 이런 단점을 보완한 것이 JWT를 이용한 토근 기반 인증 방식입니다.

TCP

인터넷이란?
TCP/IP 기반의 네트워크가 전세계적으로 확대되어 하나의 연결된 네트워크들의 네트워크 (네트워크의 결합체)입니다.

TCP란 무엇일까요?
상대방에게 정보를 보낼때

위와 같이 OSI 7계층을 거쳐서 데이터를 보낼 수 있습니다.
각 계층에 대해 설명을 해보면

  • 응용계층 : 궁, 비밀번호, 사진 100장

  • 표현계층 : 암호화, 압축

  • 세션계층 : 상대방에게 인증이 되어 있는가? 상대 컴퓨터 켜저 있는지, 접근 할 수 있는 사람인가

  • 전송계층 : TCP와 UDP 할지 결정
    여기서 TCP와 UDP가 등장합니다.

    • TCP
      A:안녕?
      B: ACK(잘받았어
      A: 반가워
      이후 ACK가 안옴
      A: 반가워 다시 보냄
      B: ACK 보내려다가 유실, 낚아채거나
      A: 다시 무언가 보냄
      신뢰성 있는 통신
      (느리다)

    • UDP
      A:안녕
      ACK 따위 신경쓰지 않고 무작위

      신뢰성 없는 통신
      (속도는 빠르다)

      그래서 UDP는 보통 사람이 이해할 수 있는 것들을 전송할 때 사용한다.

      전화: 안녕 반...워
      추측해서 이해 (반가워)

      동영상 : 어떤 화면 24장 1초-> 영상 중간에 사진이 유실되어도 사람은 동영상이
      조금 끊기더라도 이해할 수 있음 -> 왜냐하면 사람이니까

      그러나 비밀번호의 경우는 중간에 유실되면 인증이 되지 않음
      => 사람이 이해할 수 잇는 것들은 TCP로 보낸다. (또한 보안이 중요한 것들)

  • 네트워크 : IP를 알아야지 상대방을 찾을 수 있음

  • 데이터 : IP로 집을 찾아가고 거기에 공유기의 LAN 즉 공유기에 연결된 내 목적지의 컴퓨터

  • 물리 : 전기선

다시 밑에서부터 올라가서 마트료시카처럼 하나씩 깨서 상대방에게 전달

보안의 3요소 CIA

기밀성 (Confidentiality)
무결성 (Integrity)
가용성 (Availability)

A나라에서 B나라에 하나의 문서를 전달해야 합니다.
그러나 중간에 C 나라가 이 문서를 가져갑니다. => Confidentiality : 기밀성이 깨짐

C나라는 낚아채서 기존 문서를 변경해서 => Integrity : 무결성이 깨집니다.
B나라에 전달한다 => Availabilit : 가용성이 깨집니다.

그래서 문서를 그대로 보내면 안됩니다.

  1. 가용성을 유지하는 방법
    문서와 함께 최고의 전사를 보내면 문서를 뺏기지 않을 수 있다.

  2. 그러나 최고의 전사가 죽는다면 -> 기밀성 지키는 방법
    문서를 암호화 -> 가용성이 깨질 수 있어도 기밀성이 유지

  3. 암호화를 했는데 금고에 넣어서 벽에 붙여 놓기-> 가용성을 지키자

문제는 이렇게 다 했는데 그 금고를 열 수 있는 열쇠도 A나라가 B 나라에 전달해야하는데
그 열쇠도 위험하다는 것이다. C나라가 훔쳐갈 수 있다.

그래서 첫 번째 문제는 열쇠 전달의 문제를 해결하면 됩니다.

두 번째 문제는 문서가 누구로부터 왔는지 알면 해결됩니다.

만약에 청군a(100) 홍군(150) 청군b(100)
청군이 홍군을 이길 수 있는 방법은 동시에 쳐들어 가는 것입니다.
하지만 동시에 처들어 가려면 청군a는 청군b에 몇시 몇분에 처들어 가야한다고
문서를 보내야 합니다.

홍군이 이 문서를 위조할 수 없다고 가정할 때
청군 a는 청군 b로부터 ACK를 받아야 몇 시 몇 분에 처들어갈 수 있습니다.
그러나 만약에 중간에 홍군이 문서를 가져가거나 가져가서 청군 b 대신에 ACK를 보내거나
혹은 청군 b가 보내는 ACK를 낚아챈다면

동시에 처들어갈 수 없습니다.

따라서 아래의 두 가지 문제가 해결된다면
문서의 CIA를 지킬 수 있습니다!

  1. 열쇠 전달의 문제 누군가 열쇠를 가로채서 암호화된 내용을 풀지 못함
  2. 누구에게 왔는지 인증 문제 가로채서 내용을 변경해서 보내버림

이 두 문제를 해결해 줄 방법이 있습니다.
바로 RSA라는 암호화 방식입니다.

RSA

RSA라는 암호

Public Key : 공개키
Private Key : 개인키

A의 공개키
A의 개인키

B의 공개키
B의 개인키

  1. 열쇠 전달 문제 해결
    A ➡️ "사랑해" 를 B의 공개키로 암호화 ➡️ B는 사랑해는 자신의 개인키로 풉니다
  1. 누가 전달했는가?
    A ➡️ A의 개인키로 (C에게 1억을 송금했다)를 암호화 ➡️ B는 A의 공개키로 이것을 풀 수 있습니다.
    즉 A의 공개키로만 암호를 풀 수 있으므로 A가 보냈다는 것을 알 수 있습니다.
    이것을 전사서명에서 사용하는 인증 방식입니다.

공개키-> 개인키 => 암호화
개인키 -> 공개키 => 전자서명

즉 A가 자신의 비밀번호 123을 B에게 보내는 방법

[A개인키(B공개키:123)]
1. B는 저걸 받으면
2. A의 공개키로 연다
3-1 열어진다면 A가 보낸 것이다.
3-2. 열어지지 않는다면 누군가 수정해서 누군가의 개인키로 암호화한 것이다.
4. 열어졌다면 B의 개인키로 암호를 푼다.

RFC (Request for Comments)

인터넷 기술을 연구하는 사람 관련 메모를 작성해서 인터넷국제표준화기구(IETF)에 보내면 IETF에서 검토하여 정식 RFC로 등록할지 결정한다. 검토 등을 통해 문서에 대한 신뢰가 높아지면 해당 RFC에 번호를 붙이는데, 이렇게 되면 사실상 표준이 된다

통신에 대한 규칙이 정해진 문서
이 약속된 규칙을 프로토콜이라고 합니다.

새로운 네트워크들이 연결될 때마다 문서들이 생기고
이 문서에 번호를 부여하게 됩니다.

대표적으로 웹의 기반이 되는 프로토콜인 HTTP 1.1 은 RFC-2068 에서 시작됐으며 이를 개선한 RFC-2616 도 발표되었고 메일을 전송하는 프로토콜인 SMTP(Simple Mail Transfer P{rotocol) 는 RFC-821 에서 시작됐습니다.

이런 네트워크들이 모인 것들이 www = RFC의 총집합 = 인터넷

RFC 7519에 정의된 것이 JWT입니다.

JWT (Json Web Token)

앞서 말했듯이 JWT는 세션의 단점을 보완해준다고 했습니다.
JWT는 세션처럼 한곳에서 세션ID를 관리할 필요가 없기 때문입니다.

강의에서는 JWT를 로컬 스토리지에 저장한다고 했으나
그 부분에 의문이 생겨 찾아보게 되었습니다.
저의 궁금증을 해결해 줄 글을 하나 발견하게 되어 링크를 남깁니다.

JWT는 어디에 저장해야할까? - localStorage vs cookie

JWT은 서명이 되어 있습니다.
서명된 토큰 = "내가 보낸 토큰이 내가 보낸 토큰이 맞어"라는 것에 중점

구조 xxxxx.yyyyy.zzzzz = header . payload . signature

⏺️ Header
어떤 알고리즘을 이용해서 서명했는지

The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.

두 개의 부분으로 이루어져 있습니다.
토큰의 타입과 알고리즘
알고리즘은 뒤에 나오지만 <Header + payload + 서버 시크릿 코드>를 암호화하는 알고리즘입니다. 알고리즘 종류는 HMAC SHAR256 또는 RSA이 있습니다.

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

this JSON is Base64Url encoded to form the first part of the JWT.
JSON은 Banse64(바이너리 데이터를 문자 코드에 영향을 받지 않는 공통 ASCII 문자로 표현하기 위해 만들어진 인코딩)로 인코딩되어진다고 합니다.

⏺️ payload
클래임을 가지고 있습니다.
클레임은 통상적으로 user와 같은 개체와 추가적인 데이터 대한 statement입니다.
3가지의 클레임을 가지고 있습니다.

등록된 클레임 : 필수는 아니고 권장
Private claims : user_id - 우리 유저에 대한 정보 공개되도 되지만 유저임을 알 수있는 내용

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

이 payload로 Base64Url로 인코딩됩니다.

⏺️ Signature

To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.

해석해보면
Signature = Header에 지정된 알고리즘 <인코딩 된 Header + 인코딩 된 payload + 시크릿 코드>으로 서명

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

서명은 메시지가 도중에 변경되지 않았는지 확인하는 데 사용되며, 개인 키로 서명된 토큰의 경우 JWT의 보낸 사람이 누구인지 확인할 수도 있습니다.

클라이언트에서
ssar : 1234를 보내면
이제 서버는 JWT 토큰을 만드는데

  • hedaer -> H256
    (H256이란 HMAC의 방식으로 SHA256을 이용하여 HASH하겠다는 의미
    HMAC : 시크릿 키를 포함 + SHA256 : Hash)
  • payload: username = ssar
  • signiture : [hader + payload + 서버의 시크릿코드] H256알고리즘으로 암호화

그리고
header Banse64 인코딩
payload Banse64 인코딩
signiture = H256[header Banse64 인코딩+payload Banse64 인코딩 +시크릿 코드]
클라이언트에 보낸다

클라이언트를 이를 웹 브라우저의 로컬 스토리지에 저장합니다.

이제 클라이언트가 서버에 JWTr가 있습니다.
내 개인정보를 줘라고 서버에 request

이제 서버는 자신이 받은 JWT가 유효한 토큰인가를 확인해야 합니다.
(그 안에 든 내용이 중요하지 않다 단순히 유효한 토큰인가를 확인해야 한다.)
따라서 JWT에 있는 header+ payload+그리고 자신이 가지고 있는 시크릿코드를
H256방식으로 암호화한다.

자신이 H256 암호화한 (header+payload+시크릿코드) == (JWT에 있는 시크니처) 확인
같으면 이전에 로그인 한 애가 맞아 라고 인증
이후에는 payload에 있는 username을 가지고 가서
DB에 검색해서 결과를 클라이언트에 돌려줍니다.

RSA로도 가능하다
JWT를 만들 때 pay+header를 서버의 개인키로 암호화
그리고 다시 서버에 jWT로 인증을 요청할 때 공개키로 복호화

유효한 데이터인지만 중요하다
즉 기밀성이 아닌 유효한 데이터인가만

reference

최주호님의 스프링 시큐리티 + JWT 강의
https://jwt.io/

profile
꾸준하게 Ready, Set, Go!

0개의 댓글