JWT (Json 웹 토큰)
JSON 웹 토큰(JWT)은 온라인 네트워크에서 정보를 안전하게 통신할 때 사용하는 인터넷 표준 토큰이다.
JWT는 간결하고 URL에 안전하게 사용할 수 있다는 장점이 있다.
JWT는 인증, 정보 교환 등 다양한 용도에 사용된다. 이때 주고받는 정보를 클레임(Claim)이라고 하고, 클레임의 집합은 JSON 객체로 표현한다.
JWT의 두 가지 유형
JWT는 JWS 또는 JWE로 구현되어야 한다. JWT는 JWE와 JWS를 아우르는 인터페이스 같은 존재이고, 실제 구현은 JWS와 JWE로 나누어지는 것이다.
JWT는 토큰이고 JWE와 JWS는 그 평문 토큰을 더 안전하게 만들어주는 추가 구현이다. JWE 혹은 JWS 방식 중 하나를 필수적으로 선택해야지만 JWT라고 부를 수 있다.
Claim은 서명을 하거나 암호화하여 사용한다. 디지털 서명을 하는 방식이 JWS(JSON Web Signature) 방식이고 암호화 하는 방식이 JWE(JSON Web Encryption)이다.
더 널리 사용되는 첫 번째 방식은 JWS(JSON Web Signature)이다. JWS 방식을 사용하면 클레임의 내용은 누구나 읽을 수 있지만, 서명이 있기 때문에 데이터의 무결성이 보장된다.
두 번째 방식은 JWE(JSON Web Encryption)이다. JWE에서는 클레임 자체를 암호화한다. 그래서 복호화 방법을 알고 있는 사용자만 페이로드를 읽을 수 있다.
보안 측면에서는 JWE가 더 안전하다. 그러나 페이로드의 데이터를 클라이언트가 바로 사용해야 된다면 JWS가 더 편리하고 데이터의 무결성을 보장할 수 있다.
→ 용도의 보안, 사용성을 고려해서 방식을 선택하면 된다.
JWT의 구성
JWS, JWE 둘 다 구성은 같다. 헤더, 페이로드, 서명 세 가지 주요 요소(part)로 구성되고 각 구성은 . 마침표를 구분자로 사용한다.
실제 JWS는 줄바꿈이 없는 문자열이다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // 헤더
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. // 페이로드
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // 서명
- 헤더(Header): 일반적으로 헤더에는 토큰의 유형(JWS, JWE)과 서명 알고리즘을 명시해요. JSON으로 표현된 헤더를 Base64로 인코딩한 것이 JWT 헤더입니다.
- 페이로드(Payload): 보통 JSON 형식으로 표현된 사용자의 정보나 클레임이 키-값(key-value)로 포함된 부분입니다. RFC 7519에 정의된 iss(issuer), exp(expiration time), sub(subject), aud(audience) 등 키를 사용할 수 있지만, 필요에 따라 새로운 클레임을 추가할 수도 있어요. JWS 방식에서는 페이로드도 Base64로 인코딩합니다. 누구나 디코딩할 수 있기 때문에 JWS 페이로드에는 민감한 정보를 넣으면 안 돼요. JWE 방식에서는 페이로드를 안전한 알고리즘과 비밀 키로 암호화하기 때문에 민감한 정보를 포함할 수 있어요.
- 서명(Signature): 헤더와 페이로드를 결합한 후 지정된 알고리즘과 비밀 키 또는 공개 키로 서명한 값입니다. 이 서명은 JWT의 무결성을 보장하며, 데이터가 변경되지 않았음을 확인할 수 있습니다. 서명을 아래와 같은 형태입니다. 인코딩한 헤더, 페이로드를 헤더에 정의한 알고리즘에 의해 secret(키)으로 암호화합니다. 서명은 키로만 복호화할 수 있기 때문에 토큰의 전송자와 내용의 무결성을 보장합니다.
JWT도 제 3자에게 토큰 탈취의 위험성이 있기 때문에, 현업에서는 그대로 사용하는 것이 아닌 Access Token, Refresh Token으로 이중으로 나누어 인증을 하는 방식을 취한다.
토큰이 어디에 저장되고 관리되느냐에 따른 사용 차이이다.
- Access Token: 클라이언트가 갖고 있는 실제로 유저의 정보가 담긴 토큰으로, 클라이언트에서 요청이 오면 서버에서 해당 토큰에 있는 정보를 활용하여 사용자 정보에 맞게 응답을 진행
- Refresh Token: 새로운 Access Token을 발급해주기 위해 사용하는 토큰으로 짧은 수명을 가지는 Access Token에게 새로운 토큰을 발급해주기 위해 사용. 해당 토큰은 보통 데이터베이스에 유저 정보와 같이 기록
- 클라이언트가 로그인 시 서버에 아이디, 비밀번호 같은거 보내면서 로그인 요청
- 서버는 access token 발급하고 세션 생성 (세션: 클라이언트와 서버 간의 상태 유지하는 정보, 세션을 통해 서버는 클라이언트의 로그인 상태, 인증 정보 등 기억할 수 있음)
- 서버는 refresh token을 세션과 연결해서 내부에서 관리
- access token은 보안상의 이유로 몇 분 ~ 몇 시간 동안 유효하기에 시간 만료되면 서버로부터 "401 Unauthorized" 응답을 받음
- 그러면 클라이언트는 동일한 API 요청 다시 시도하거나 서버에 특정 재발급 요청 엔드포인트로 요청을 보냄 (클라이언트는 별도의 refresh token을 보내지 않으며, 세션 ID(보통 쿠키로 전달)를 통해 요청을 보낸다.)
- 서버는 클라이언트의 세션을 확인하고 해당 세션에 저장된 refresh token을 사용해서 새로운 access token을 발급하고 클라이언트에게 반환
- 클라이언트는 이 access token을 사용해서 다시 요청
우리팀은 JWT 발급, 관리를 Spring 서버에서 했지만 클라이언트 body에 토큰을 보내고, 그걸 Next.js 서버가 받아서 Spring 서버로 그대로 보내기만 하는 방식을 취해서 보안이 취약하다는 문제가 있었다.
이를 보완하기 위해서는 Next.js가 요청 처리 전에 JWE를 검증하는 로직이 있어야 함을 알았다.
[출처]
'Web Programming' 카테고리의 다른 글
[JUnit] assertThat(AssertJ), assertEquals(JUnit) 비교 (0) | 2025.01.10 |
---|---|
Spring Batch와 스케줄러 (0) | 2024.11.25 |
[Spring] DI 의존성 주입 | Field 주입, Setter 주입, 생성자 주입 (1) | 2024.09.04 |
[Java] HikariCP | Database Connection Pool (0) | 2024.08.27 |
CORS의 모든 것 | CORS란? | CORS 시나리오와 대응 방법 (1) | 2024.08.06 |