JWT

양치는 하셨나요·2024년 8월 13일

지난 글에 이어 이번에도 보안 관련 글이다. JWT는 쿠키 세션 토큰 글에서 간략히 다룬 적이 있지만 이것 자체도 내용이 꽤 되어서 따로 글을 작성한다. 이왕 작성하는거 더 자세히 다뤄본다.

JWT

개념

Token 방식 중 가장 많이 사용되는 것이 JWT로 JSON 포멧으로 저장하는 web Token이다.

정보를 비공개로 전달하거나 인증할 때 주로 사용한다.

→ 클라이언트와 서버 사이에서 인증 및 인가 시 사용하는 토큰이다.

구조

JWT는 점으로 3개의 영역(header, payload, signature)을 구분한다.

JWT를 어떻게 검증하는지에 대한 내용. JWT의 정보

서명 시 사용하는 알고리즘, 사용하는 키를 식별하는 값 등이 여기에 들어간다.

{
	"alg": "RSA",
	"typ": JWT
}
  • alg : Signature를 해싱하기 위한 알고리즘을 지정
  • typ : 토큰의 타입을 지정
    • ex) JWT

payload

JWT의 내용이다. 여기에 있는 속성들을 Claim이라고 하며 이곳에는 토큰 생성자의 정보, 생성일 등 클라이언트와 서버 간 주고 받기로 약속한 데이터들이 담겨있다.

Claim 종류

  • Registered Claim(등록된 클레임)
    • 토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터
    • 종류
      • iss: 토큰 발급자(issuer)
      • sub: 토큰 제목(subject)
      • aud: 토큰 대상자(audience)
      • exp: 토큰 만료 시간(expiration)
      • nbf: 토큰 활성 날짜(not before)
      • iat: 토큰 발급 시간(issued at)
      • jti: JWT 토큰 식별자(JWT ID)
        • 중복 방지를 위해 사용
        • 일회용 토큰(Access Token) 등에 사용
    • 결국 모두 선택사항이다.
  • Public Claim(공개 클레임)
    • 사용자 정의 클레임
    • 공개용 정보를 위해 사용
    • 충동방지를 위해 URI포맷을 이용
  • Private Claim(비공개 클레임)
    • 사용자 정의 클레임
    • 서버와 클라이언트 사이에 임의로 지정한 정보를 저장

signature

점(.)을 구분자로 한 헤더와 페이로드를 합친 문자열을 서명한 값. 사실상 가장 중요한 부분이다.

header에서 정의된 알고리즘을 이용해 payload와 서버가 가진 고유한 key 값을 합친 것을 암호화한다.

Header 와 페이로드는 단순히 인코딩된 값이기 때문에 제 3자가 복호화 및 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없기 때문에 토큰의 위변조 여부를 확인하는데 사용할 수 있다.

장점

  • header와 payload 를 가지고 signaure를 생성하므로 데이터 위변조를 막을 수 있다.
  • 인증 정보에 대한 별도의 저장소가 필요 없다. (서버 부하 ↓)
  • JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증 되었다는 서명 등 필요한 모든 정보를 자체적으로 지니고 있다.
  • 토큰은 한 번 발급 되면 유효 기간이 만료될 때까지 계속 사용이 가능하다.

단점

  • 쿠키나 세션과 다르게 JWT는 토큰의 길이가 길어 인증 요청이 많아질수록 네트워크 부하가 심해진다.
  • payload 자제는 암호화되지 않기 땨문에 유저의 중요한 정보를 담으면 안된다.
  • Key 혹은 토큰을 탈취당하면 대처하기 어렵다.
  • 특정 사용자의 접속을 강제로 만료하기 어렵지만, 쿠키/세션 기반 인증은 서버 쪽에서 쉽게 세션을 삭제 할 수 있다.

요청 방법

JWT는 인증과 관련된 것인 만큼 HTTP 프로토콜에 담겨질 것이다. 여기서 중요한 것은 어디에 담기느냐이다. Header인가 Body인가.

정답은 Header이다.

우선 길이가 그렇게 길지도 않고 JWT가 제안된 RFC7519 문서에서 Header를 이용해 보내자고 약속 했기 때문에 Header에 실어 보낸다.

이때 서버에서는 이를 받고 처리해야 하는데 이때 사용하는 것이 Oauth에서 사용하는 Bearer 인증 방식이다.

  • Oauth는 프레임워크로 인증에 관한 기능을 제공한다. 이에 대한 것은 추가 게시글로 조사하겠다.
  • Bearer인증은 소유자에게 권한을 부여해준다는 의미이다.

사용

그럼 이런 복잡한 구조들을 일일히 다 적어야 할까?

스프링에서 JWT를 구현할 땐 JJWT(Java JWT)라이브러리를 이용해 구현한다고 한다.

JJWT

자바에서 JWT를 생성, 서명, 검증, 디코딩 하기 위한 라이브러리로 해당 라이브러리에는 아래와 같은 기능들이 있따.

  • JWT 생성: Token생성, Claim을 추가 등의 기능.
  • JWT Signature: 비밀 키 혹은 공개 키를 이용해 서명 진행.
  • JWT 검증: JWT의 서명과 만료 시간 등을 검증.
  • JWT 디코딩: JWT의 payload를 디코딩해 데이터를 추출.

사용 이유

  • 직관적 API
  • Claim 설정, 서명, 검증 등의 기능을 간편하게 구현 가능
  • jjwt에 대한 문서가 상세히 정리되어 있음.
  • 라이브러리 자체가 가볍다.

JWT 고려 사항

JWT의 단점에서 볼 수 있듯 JWT는 완벽한 방식이 아니다. 그러다보니 고려해야 하는 부분들이 존재한다.

Secret Key 관리

결국 Key를 탈취당한다면 나머지의 내용들은 JSON 파일로 있기에 정보가 도난당할 수 있다. 그러니 Key에 대한 관리가 추가로 필요하다. 관리에 대한 방법은 아래의 두가지 방법이 있다.

  • KMS
    • Key Management Service의 약자. 애플리케이션의 중요 데이터를 안전하게 보호하기 위해 암호화 키를 간편하게 생성, 저장, 관리하는 서비스.
  • Key Rotation
    • 주기적으로 Key를 교체해 보안을 강화한다. 키가 교체될 때 마다 새로이 서명하고 검증해야 함을 유의해야 한다.

토큰 만료 설정

이는 JWT만의 문제가 아니라 토큰 방식의 문제인데 인증에 사용되는 토큰 자체가 도난 당한다면 막을 수 있는 방법이 없다. 그러다 보니 토큰 자체의 관리도 필요하다. 이는 토큰에 관해 정리한 글에서도 확인 할 수 있어 해당 글을 가져왔다.

  1. 짧은 만료 기한 설정

    토큰의 만료 시간이 짧으면 탈취 되더라도 금방 만료되기 때문에 피해를 최소화 할 수 있다. 하지만 사용자가 자주 로그인 해야 하는 불편함이 있다.

  2. Sliding Session

    서비스를 지속적으로 이용하는 클라이언트에게 자동으로 토큰 만료 기한을 늘려주는 방법으로, 1번의 단점을 보완해줄 수 있는 전략이다. 이는 곧 로그인을 하지 않아도 항상 유지가 되므로 보안상 취약점이 생길 수 있다.

  3. Refresh Token

    위에서 인증에 사용되는 Token을 Access Token 이라고 하고 그보다 만료 기간이 긴 Refresh Token을 따로 생성해 같이 내려준다. 클라이언트는 Access Token이 만료되었을 때, Refresh Token을 사용하여 Acess Token의 재발급을 요청할 수 있다. 서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 새로운 Access Token을 발급하고, 만료된 경우 다시 로그인 하도록 한다. 이렇게 한다면 1번과 2번의 문제를 한번에 해결할 수 있지만 검증을 위해서는 서버에 Refresh Token을 별도로 저장 시켜야 하기에 JWT의 Stateless의 장점이 없어지게 되고 구조적 구현 과정이 복잡해진다.

Token 저장 위치

당연한 것이지만 토큰이 어디에 저장되는가에 따라 고려해야 하는 부분이 달라진다.

  • 비공개 변수
    • 브라우저의 리프레시마다 초기화. 따라서 새로고침마다 새로 접속해야 한다.
  • 로컬 스토리지
    • 자주 사용하는 방식으로 새로고침 후에도 정보가 유지되어 편의성이 높다.
    • 하지만 JS 코드를 이용한 접근이 가능해 XSS공격에 취약하다.
      • XSS(Cross-Site Scripting)란 동적으로 출력하는 페이지에 대해 클라이언트 언어(Client Side Script)로 작성된 악의적인 스크립트를 삽입하여 비정상적인 행위를 하는 공격을 말한다.
  • 세션 스토리지
    • 로컬 스토리지에 비해 제한적으로 탭이 유지될 때만 정보가 유지된다.
  • 쿠키
    • 보안적 측면에서 쿠키 또한 JS로 접근 가능하기에 서버측에서 HTTP Only, secure, samesite 등의 옵션이 필요하다.
      • HTTP Only: 중간에 쿠키를 탈취하고자 하는 XSS 공격 등에 대비해 브라우저에서 쿠키로 바로 접속할 수 없도록 하는 것.

      • secure: 네트워크 직접 감청으로 인한 탈취를 막기 위해 HTTPS를 이용해 보안을 유지한다.

      • samesite: 쿠키가 사용 가능한 도메인을 지정하는 옵션으로 브라우저 주소란과 동일한 도메인에 대한 요청시에만 쿠키를 전송하는 것.

        → CSRF 공격에 대한 예방.

        CSRF: Cross Site Request Forgery의 약자. 공격자의 요청이 사용자의 요청인 것처럼 속여서 정보를 빼돌리는 것.

토큰 무효화

어찌되었든 토큰이 탈취 되었을 때 해당 토큰을 무효화할 방법이 있어야 추가적인 예방이 가능한 것은 어찌 보면 당연한 것이다. 이런 해결책에는 로그아웃 처리, 블랙리스트 관리 등의 방법이 있다.

  • 로그아웃: JWT는 결국 HTTP를 이용하므로 Stateless하다. 그러니 로그아웃 시 토큰을 무효화하는 방법도 좋은 수단이다.
  • 블랙리스트: 로그아웃된 토큰을 만료하지 않고 추가로 관리하는데 해당 토큰으로 접근 시 해당 접근을 차단할 수 있는 방도를 마련한 것이다. 하지만 이 방법은 Stateless를 해치기 때문에 신중히 사용해야 한다.

종합

위의 방법들과 언급하지 않았지만 다른 방법들까지 포함해 보안을 유지할 수 있도록 고려한다면 JWT에 대한 보안을 강화할 수 있다. 또한 각 부분들을 보면 서로 다른 부분을 보완하고 있기 때문에 다양한 방법을 혼합해 더욱 강화된 보안 전략 구축이 필요하다.


결론

JWT는 정보를 암호화해 전달하거나 인증할 때 사용하는 Token이다.

JWT 단독 뿐 만 아니라 다양한 보안 대책을 같이 활용하면 더욱 우수한 보안 안전성을 가질 수 있다.

profile
프로그래밍을 잘하고 싶어요..

0개의 댓글