[Security] JWT(Json Web Token)에 대하여

jiveloper·2024년 3월 3일
0

Security

목록 보기
3/3
post-thumbnail

0. 들어가며

안녕하세요~~😄
저번 시간에는 세션 인증 방식에 대해 알아보았는데요, 이번엔 사용자를 인증 및 인가하는 다른 방식인 토큰 인증 방식에 대해 알아보려고 합니다.

토큰 인증 방식은 클라이언트에서 인증 정보를 보관하는 방식 입니다.
저번에 배운 세션 인증 방식은 매 요청마다 세션 DB를 통해 세션id를 조회했어야했는데요, 그렇게 되면 서버에 부담이 되었기 때문에 토큰 인증 방식이 생겼습니다.

비록 클라이언트에 저장해서 보안에 대한 걱정이 들 수도 있지만, 토큰은 유저 정보를 암호화한 상태로 담기 때문에 클라이언트에 담아도 괜찮습니다.

토큰에는 여러 종류가 있습니다. 그 중에서 개방형이고 웹에서 널리 쓰이는 JWT에 대해서 알아보겠습니다. 🙂


1. JWT(Json Web Token)

1. JWT란?

JWT(Json Web Token)은 Json 객체에 인증에 필요한 정보들을 담은 후 비밀키로 서명한 토큰으로, 인터넷 표준 인증 방식 입니다.

여기에 들어가보시면 JWT를 디코딩 할 수 있고, 디코딩된 토큰을 수정하여 다시 인코딩 할 수도 있습니다.

2. JWT 구성 요소

JWT는 각각의 구성 요소가 .으로 구분이 되어있으며, header, payload, signature로 구성되어있습니다.

이 세 부분을 하나하나씩 확인하여 JWT가 어떻게 구성되어있는지 알아봅시다.

1. HEADER

Header에는 해시 암호화 알고리즘과 토큰의 유형이 담겨있습니다.

해시 암호화 알고리즘은 대표적으로 RS256(공개키/개인키)와 HS256(비밀키(대칭키))가 있습니다. (*해시 암호화 알고리즘에 대해 궁금하신 분들은 여기 참고하시면 됩니다.)

{
  "alg": "HS256", // 해시 암호화 알고리즘 (algorithm)
  "typ": "JWT" // 토큰 유형 (type)
}

🤓: 가끔 어떤 서버에서는 alg에 "none"을 넣는 경우에도 인증시켜주는 경우가 있습니다. alg 검사를 주의해서 할 수 있도록 해야합니다!

2. PAYLOAD

Payload에는 토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨 있습니다. Claim에는 어떤 값이 들어가든 개발자 재량이지만, 아래와 같은 표준 스펙으로 정의 할 수도 있습니다.

{
  "iss": "jieonist", // 토큰 발급자 (issuer)
  "sub": "1234567890", // 토큰 제목 - 사용자에 대한 식별값 (subject)
  "aud": "jiveloper", // 토큰 대상자 (audience)
  "exp": 1716239022 // 토큰 만료 시간 (expired)
  "nbf": 1716239022 // 토큰 활성 날짜 - 이 날짜 이전의 토큰은 활성화 되지 않음을 보장 (not before)
  "iat": 1709320002, // 토큰 발급 시간 (issued at)
  "jti": "id1" // JWT 토큰 식별자 - issuer가 여러명일 때 이를 구분하기 위한 값 (JWT id)
}

위의 스펙은 모두 포함해야하는 것은 아니며, 해당 서버가 가져야할 인증 체계에 따라 자유롭게 사용하시면 됩니다.

🤓: JWT는 누구나 복호화하기 쉽기 때문에, Payload에는 예민한 개인 정보를 넣으면 유출되기 쉽겠죠? 가급적 Payload에는 사용자를 식별할 수 있는 아이디 정도만 저장하는 것이 좋으며, 해당 사용자에 대한 추가 정보가 필요한 경우에는 서버에서 사용자 DB를 조회하는 것이 안전할 것입니다. 불가피한 이유로 Payload에 민감한 사용자 정보를 저장해야한다면 반드시 암호화를 하여 JWT 토큰을 디코딩한 후에도 알아볼 수 없게 해야할 것입니다.

3. SIGNATURE

Signature에는 가장 중요한 서명 값이 담겨있는데요,
Header + Payload + Header에서 선언한 알고리즘 + key를 합쳐 암호한 값이 담겨있습니다.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload), 
  [your-256-bit-secret]
) secret base64 encoded

Header와 Payload는 단순히 base64Url로 인코딩되어 있어 누구나 쉽게 복호화할 수 있지만, Signature는 key가 없으면 복호화할 수 없습니다. 개인키로 서명했다면 공개키로 유효성 검사를 할 수 있고, 비밀키로 서명했다면 비밀키를 가지고 있는 사람만이 암호화 복호화, 유효성 검사를 할 수 있습니다.

🤓: secret key를 흔한 값으로 하게 되면 brute force attack을 당할 수 있습니다.


2. JWT의 토큰 인증 방식

1. 동작 과정

서버와 클라이언트간의 토큰을 통한 통신 과정을 조금 더 자세히 알아보겠습니다.

1. 사용자 인증하기! (feat. 토큰 생성 과정)

JWT를 통해 어떻게 인증이 되는지 그림을 통해 알아봅시다.

  1. 클라이언트에서 사용자는 아이디(id) 및 비밀번호(pw)를 입력합니다.
  2. 클라이언트는 서버로 사용자가 입력한 아이디와 비밀번호를 전달합니다.
  3. 서버는 전달 받은 정보를 바탕으로 유저가 유효하다면 비밀키를 사용해 json 객체를 암호화한 JWT를 발급합니다.
    (*유저가 유효하지 않다면 로그인을 실패한 것이므로 토큰을 발급하지 않습니다.)
  1. 토큰이 발급되었다면 서버는 클라이언트로 토큰과 response를 전달합니다.
  • 토큰을 헤더 혹은 response로 보냅니다.
    (* 만약 토큰이 발급되지 않았다면 로그인에 실패했다는 에러 메세지가 담긴 response를 보낼 것입니다.)
  1. 클라이언트는 JWT를 저장합니다.

2. 데이터 송수신 방법

이제 클라이언트는 토큰을 받아서 인증도 했겠다, 서버로부터 인가도 받아보겠습니다.

먼저, 클라이언트에서 서버로 데이터를 요청해보겠습니다.

  • 데이터 송신 방법

    1. 클라이언트는 저장된 JWT 토큰을 Authorization 헤더에 담아 서버에 데이터를 요청합니다.
    2. 서버는 헤더를 매번 확인하여 JWT의 만료 기간 등 JWT가 신뢰할만한지 체크합니다.
  • 데이터 수신 방법

    3. 서버는 reqeust에 대한 response를 전달합니다.

    🙂: JWT가 유효하면 인증된 사용자이므로 사용자에게 필요한 기능을 인가하고, 유효하지 않으면 인증 처리를 해주지 않습니다. 당연히 필요한 기능에 대해 인가 받지 못 할테구요.


2. refresh token을 활용한 토큰 리프레시

만약 클라이언트에서 만료된 JWT를 가지고 서버에 api 요청을 하면 어떻게 될까요? 서버에서는 토큰을 재발급 받을 수 있도록 해주어야 할 것입니다. 토큰을 발급 받으려면 로그인을 해야하는데, 그럼 토큰이 만료될 때마다 재로그인을 해야하는 것일까요? 일반적인 서비스를 생각해보았을 때, 서비스 이용 도중에 로그인이 풀리면 매우 불편할 것입니다.
(물론, 은행이나 정부24 같이 개인 정보가 예민한 서비스는 시간을 따로 연장하지 않는 이상 로그인이 금방 풀려 재로그인을 하는 것이 일반적입니다.)

일반적인 서비스에서 재로그인을 하지 않아도 만료된 토큰을 갱신 시키는 방법에 대해 알아봅시다. 🤔

🤓: 우리는 api를 요청할 때마다 Authorization 헤더에 토큰을 실어서 요청해야하므로, 토큰이 만료 되면 새로운 토큰을 발급 받아 새 토큰을 헤더에 넣어주어야겠습니다. Authorization 헤더에 넣는 토큰을 accessToken이라고 하며, 그 accessToken을 갱신 시키기 위한 토큰을 refreshToken이라고 합니다.

1. 사용자 인증하기! (feat. 토큰 생성 과정)

로그인을 통해 사용자를 인증하는 방법은 위에서 본 것과 유사합니다. 다만, JWT가 accessToken으로 대체 되었고, accessToken 재발급을 위한 refreshToken이 추가되었다는 점이 다릅니다! 😄

로그인을 할 때는 JWT 대신 accessToken과 refreshToken을 발급 시켜줍니다. 클라이언트는 accessToken과 refreshToken 모두 저장합니다.

2. 데이터 송수신 방법

데이터 송수신 방법도 위에서 본 것과 유사합니다. 다만, JWT가 accessToken으로 대체 된 점이 다릅니다! 😄

  • 데이터 송신 방법

    클라이언트는 저장된 accessToken을 Authorization 헤더에 담아 서버에 데이터를 요청합니다.
  • 데이터 수신 방법

3. refreshToken으로 accessToken 갱신하기

1, 2번은 처음 위에서 소개드린 JWT 동작 과정과 매우 유사해서 크게 다를 것이 없었습니다. 이제부터 소개드릴 것이 ⭐️ 뽀인뜨 ⭐️ 인데요! 🤩

만료된 accessToken을 보낸 경우, refreshToken으로 accessToken을 갱신시키는 방법입니다!!

자자.. 차근차근 알아봅시다 ㅎ

  1. 클라이언트에서 만료기간이 끝난 accessToken을 담아 api를 요청합니다.
  2. 서버에서는 accessToken을 검증하고, 유효하지 않음을 판단합니다.
  3. 서버에서 클라이언트로 401 에러 코드를 반환합니다.
    (*401 status code는 권한이 없다는 응답 코드입니다. 더 궁금하시면 여기를 참고하시면 됩니다.)
  1. 401 에러코드를 받은 클라이언트는 refreshToken을 가지고 accessToken을 갱신시키는 api를 호출합니다.
  2. 해당 api에서는 새 accessToken을 발급해줍니다.
  3. 클라이언트는 기존 accessToken 대신 새 accessToken을 저장합니다.
  1. 클라이언트는 Authorization 헤더에 새 accessToken을 담아 1번에서 에러 났던 api를 재요청 합니다.
  2. 서버에서 accessToken 유효성 검증을 합니다.
  3. 서버는 reqeust에 대한 response를 전달합니다.

😊: 한 줄로 요약하자면!
api 요청시 401 에러를 반환 받으면 새 accessToken을 갱신 받아 api를 재요청 한다.


3. 장단점

1. 장점

  • 세션 인증 방식과는 달리 매 요청마다 서버에서 DB를 조회하지 않고 토큰의 유효성 검사만 하면 되므로, 세션 인증 방식보다 서버의 부하가 적게 듭니다.
  • JWT를 클라이언트에 저장하기 때문에 서버 용량에 영향을 끼치지 않습니다.
  • 사용자가 늘어나더라도 사용자 인증을 위해서 추가로 투자해야하는 인프라 비용을 크게 절감할 수 있습니다.

2. 단점

  • 아까 보셨듯이 여기에 들어가보시면 JWT를 디코딩하기 매우 쉽기 때문에 예민한 정보를 넣으시면 개인 정보가 유출될 가능성이 있습니다.
  • 강제 로그아웃과 같은 세션이 필요한 기능은 당연히 사용할 수 없습니다.
  • 토큰이 한번 탈취되면 토큰의 만료 기간이 끝날 때까지 토큰은 누군가로부터 사용될 수 있습니다...

    ❗️ 예방법
    1.JWT를 HttpOnly를 설정한 쿠키에 저장한다.
    2.JWT 블랙리스트를 추가한다.
    -> 이 방법은 매번 JWT를 받아 블랙리스트에 있는지 조회하는 방법인데, 이러면 토큰 인증 방식만의 장점을 잃게 된다. (세션 토큰 인증 방식과 다를게 없어짐)
    3.JWT의 만료 시간을 짧게 설정하고 매번 JWT를 다시 재발급할 수 있는 refresh Token 방식을 도입한다.


3. 느낀점

회사에서 기존 앱에서 쓰이던 JWT 인증 방식을 OAuth로 적용시킨 경험이 있는데, 이 때 처음으로 토큰을 갱신 시키는 방법을 공부해보았다. JWT에 대해 깊게는 몰라 이번에 공부하면서 OAuth 서버 없이도 토큰을 갱신시킬 수 있다는 것을 알게 되었다!! 😅 포스팅을 하면서 새로 알게 된 내용도 있었고 refreshToken 갱신 하는 쪽은 다시 복습하는 기분을 느꼈다. 잊고 있던 부분도 있어서 다시 짚는 시간을 가졌다.

요즘 짐 같이 쌓여있던 포스팅을 하나하나 마무리 하는 중이다... 뿌듯하다...
미션을 하나하나 클리어 하는 기분이랄까?

밀려있는 다른 포스팅 들도 얼른 게시해야겠다!!!!




참고

https://velog.io/@chuu1019/%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-JWTJson-Web-Token#%EB%93%A4%EC%96%B4%EA%B0%80%EA%B8%B0

https://youtu.be/XXseiON9CV0?si=scrJjWUjKPC_a8vJ

https://velog.io/@boo1996/Token#-token-%EC%9D%B4%EB%9E%80

https://www.daleseo.com/jwt/
아이콘
https://icon-icons.com/ko/
https://www.flaticon.com/kr/

profile
👩🏻‍💻 Clean Code와 Refactoring에 관심이 많은 개발자 입니다.

0개의 댓글