처음부터 차근차근

[JWT] JWT란? 본문

CS/인증 및 인가

[JWT] JWT란?

HangJu_95 2023. 12. 8. 15:41
728x90

JWT란?

JWT(JSON Web Token)이란 인증에 필요한 정보들을 암호화시킨 JSON 토큰을 의미합니다. 그리고 JWT 기반 인증은 JWT 토큰을 HTTP 헤더에 실어 서버가 클라이언트 식별하는 방식을 의미합니다.

 

JWT는 JSON 데이터를 Base64 URL-safe Encode를 통해 인코딩하여 직렬화한 것을 의미합니다.

사실 기본적으로 인증하는 방식은 일반 Token 방식과 크게 다르지 않습니다. 다만 이 토큰 내부에는 위변조 방지를 위해 개인키를 통한 전자서명도 들어가 있습니다. 따라서 사용자가 JWT를 서버로 전송하면 서버는 성명을 검증하는 과정을 거치게 되며, 완료되면 요청한 응답을 돌려줍니다.

Base64 URL-safe Encode 는 일반적인 Base64 Encode 에서 URL 에서 오류없이 사용하도록 '+', '/' 를 각각 '-', '_' 로 표현한 것이다.

JWT의 구조

JWT의 구조

 

JWT는 각각의 구성요소가 점(.)으로 구분되어 있으며, 구성 요소는 다음과 같습니다.

 

  • Header
  • Payload
  • Signature

1. Header

Header에 들어가는 내용

header에는 보통 토큰의 타입이나, 서명 생성에 어떤 알고리즘이 사용되었는지 저장합니다.

  • alg : 서명 암호화 알고리즘(ex: HMAC SHA256, RSA)
  • typ: 토큰 유형

2. Payload

Payload에 들어가는 내용

Payload에는 토큰에 사용할 정보의 조각인 Claim이 저장되어 있습니다.

사용자에 대한, 혹은 토큰에 대한 property를 key-value의 형태로 저장하며 이를 Claim이라고 합니다.

즉, 서버와 클라이언트가 주고받는 시스템에서 실제로 사용될 정보에 대한 내용을 담고 있는 섹션이라고 할 수 있습니다.

 

Payload에 정해진 데이터 타입은 없지만, JWT의 표준 스펙이 존재합니다. 대표적으로 Registered claims, Public claims, Private claims 이렇게 세 가지로 나뉩니다.

  • Registed claims : 미리 정의된 클레임입니다.
    • iss: 토큰 발급자 (issuer)
    • sub: 토큰 제목 (subject)
    • aud: 토큰 대상자 (audience)
    • exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야합니다.
    • nbf: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념입니다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다.
    • iat: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있습니다.
    • jti: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용합니다.
  • Public claims : 사용자가 정의할 수 있는 클레임 공개용 정보 전달을 위해 사용됩니다.
    공개 클레임들은 충돌이 방지된 이름을 가지고 있어야 합니다. 충돌을 방지하기 위해서는 클레임 이름을 URI 형식으로 짓습니다.
{
    "https://velopert.com/jwt_claims/is_admin": true
}
  • Private claims : 해당하는 당사자들 간에 정보를 공유하기 위해 만들어진 사용자 지정 클레임. 외부에 공개되도 상관없지만 해당 유저를 특정할 수 있는 정보들을 담습니다.
{
    "username": "velopert"
}

 

이를 예시로 표현하자면 다음과 같습니다.

{
    "iss": "velopert.com",
    "exp": "1485270000000",
    "https://velopert.com/jwt_claims/is_admin": true,
    "userId": "11028373727102",
    "username": "velopert"
}

표준 스펙 외에도 필요하다 싶으면 추가해도 문제는 되지 않습니다. 예를 들어 Access Token과 Refresh Token을 구분하고 싶다면 "token_type"라는 커스텀 Claim을 만들고 값을 아래와 같이 "access Token"으로 두어 구분 지어도 상관없습니다.

{
	"token_type" : "access token"
}

 

다만, 가장 중요한 것은 payload에 민감한 정보를 담지 않는 것입니다.

위에 header와 payload는 json이 디코딩되어있을 뿐이지 특별한 암호화가 걸려있는 것은 아니기 때문에 누구나 jwt를 가지고 디코딩을 한다면 header나 payload에 담긴 값을 알 수 있기 때문입니다.

3. Signature

Signature를 디코딩했을 때 볼 수 있는 화면

JWT에서 가장 중요한 것은 서명입니다.

이 Signature에는 Header의 인코딩 값과 Payload의 인코딩 값을 합친 후 이를 서버가 가지고 있는 개인키를 이용하여 암호화를 진행합니다.

따라서 Signature는 서버에 있는 개인키로만 암호화를 풀 수 있으므로 다른 클라이언트는 임의로 Signature를 복호화할 수 없습니다.

Signature를 복호화 할 수 없는 이유

Signature는 JWT인증의 핵심입니다.

  • JWT 토큰을 클아이언트가 서버로 요청과 동시에 전달합니다.
  • 서버가 가지고 있는 개인키를 가지고 Signature를 복호화한 다음, Base64UrlEncode(header)가 JWT의 header와 일치하는지, base64UrlEncode(payload)가 일치한 지 확인하여 일치한다면 인증을 허용합니다.

만약 클라이언트가 payload에 담긴 식별자가 변조된 JWT로 요청을 하더라도 서버가 애초에 발급했던 Signature 안의 payload와 다르기 때문에 인증이 불가능해집니다.

  1. 다른 유저가 B를 임의로 수정 -> 유저 JWT: A.B'.C
  2. 수정한 토큰을 서버에 요청을 보내면 서버는 유효성 검사 시행
    • 유저 JWT: A.B'.C
    • C에 담겨져있는 B가 B'와 다르니 조작 확인
  3. 대조 결과가 일치하지 않아 유저의 정보가 임의로 조작되었음을 알 수 있다.

Express를 통한 JWT 맛보기

다음은 JWT를 발급받고, JWT.IO를 통해 복호화를 해보겠습니다.

app.post('/login', async (req, res) => {
    // 사용자 정보
    const user = {
        userId: 203,
        email: 'archepro84@gmail.com',
        name: '이용우',
    };

    // 사용자 정보를 JWT로 생성
    const userJWT = await JWT.sign(
        user, // user 변수의 데이터를 payload에 할당
        'secretOrPrivateKey', // JWT의 비밀키를 secretOrPrivateKey라는 문자열로 할당
        { expiresIn: '1h' } // JWT의 인증 만료시간을 1시간으로 설정
    );

    // userJWT 변수를 sparta 라는 이름을 가진 쿠키에 Bearer 토큰 형식으로 할당
    res.cookie('sparta', `Bearer ${userJWT}`);
    return res.status(200).end();
});

app.listen(5002, () => {
    console.log(5002, '번호로 서버가 켜졌어요!');
});

발급받은 JWT 토큰

 

서버키를 입력하기 전의 모습

서버키를 입력하기 전 모습에도, Payload에는 User에 대한 정보가 담겨져 있습니다.



서버키를 입력하고 난 모습

서버키를 입력한 후에, Header에는 암호화 알고리즘이 담겨져 있었으며,

Header와 Signature의 Base64Url Encode가 변경된 것을 알 수 있습니다.

JWT를 이용한 인증 과정

  1. 사용자가 ID, PW를 입력하여 서버에 로그인 인증을 요청한다.
  2. 서버에서 클라이언트로부터 인증 요청을 받으면, Header, PayLoad, Signature를 정의한다.Hedaer, PayLoad, Signature를 각각 Base64로 한 번 더 암호화하여 JWT를 생성하고 이를 쿠키에 담아 클라이언트에게 발급한다.
  3. 클라이언트는 서버로부터 받은 JWT를 로컬 스토리지에 저장한다. (쿠키나 다른 곳에 저장할 수도 있음)API를 서버에 요청할때 Authorization header에 Access Token을 담아서 보낸다.
  4. 서버가 할 일은 클라이언트가 Header에 담아서 보낸 JWT가 내 서버에서 발행한 토큰인지 일치 여부를 확인하여 일치한다면 인증을 통과시켜주고 아니라면 통과시키지 않으면 된다.인증이 통과되었으므로 페이로드에 들어있는 유저의 정보들을 select해서 클라이언트에 돌려준다.
  5. 클라이언트가 서버에 요청을 했는데, 만일 액세스 토큰의 시간이 만료되면 클라이언트는 리프래시 토큰을 이용해서
  6. 서버로부터 새로운 엑세스 토큰을 발급 받는다.

JWT의 장점

  1. Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있다.
  2. 인증 정보에 대한 별도의 저장소가 필요없다.
  3. JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증됬음을 증명하는 서명 등 필요한 모든 정보를 자체적으로 지니고 있다.
  4. 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태(Stateless)가 되어 서버 확장성이 우수해질 수 있다.
  5. 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능하다. (쿠키와 차이)
  6. OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있다.
  7. 모바일 어플리케이션 환경에서도 잘 동작한다. (모바일은 세션 사용 불가능)

JWT의 단점

  1. Self-contained : 토큰 자체에 정보를 담고 있으므로 양날의 검이 될 수 있다.
  2. 토큰 길이 : 토큰의 Payload에 3종류의 클레임을 저장하기 때문에, 정보가 많아질수록 토큰의 길이가 늘어나 네트워크에 부하를 줄 수 있다.
  3. Payload 인코딩 : payload 자체는 암호화 된 것이 아니라 BASE64로 인코딩 된 것이기 때문에, 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있으므로, payload에 중요 데이터를 넣지 않아야 한다.
  4. Store Token : stateless 특징을 가지기 때문에, 토큰은 클라이언트 측에서 관리하고 저장한다. 때문에 토큰 자체를 탈취당하면 대처하기가 어렵게 된다.

참조

 

JWT(Json Web Token) 알아가기

jwt가 생겨난 이유부터 jwt의 실제 구조까지 | 사실 꾸준히 작성하고 싶었던 글이지만 JWT를 제대로 개념을 정리하고 구현을 진행해본 적이 없었는데 리얼월드 프로젝트를 진행하면서 JWT에 대한

brunch.co.kr

 

🌐 JWT 토큰 인증 이란? (쿠키 vs 세션 vs 토큰)

Cookie / Session / Token 인증 방식 종류 보통 서버가 클라이언트 인증을 확인하는 방식은 대표적으로 쿠키, 세션, 토큰 3가지 방식이 있다. JWT를 배우기 앞서 우선 쿠키와 세션의 통신 방식을 복습해

inpa.tistory.com

 

[JWT] JSON Web Token 소개 및 구조 | VELOPERT.LOG

지난 포스트에서는 토큰 기반 인증 시스템의 기본적인 개념에 대하여 알아보았습니다. 이 포스트를 읽기 전에, 토큰 기반 인증 시스템에 대해서 잘 모르시는 분들은 지난 포스트를 꼭 읽어주세

velopert.com

 

'CS > 인증 및 인가' 카테고리의 다른 글

[JWT] Access Token & Refresh Token  (1) 2023.12.08
[인증 및 인가] Cookie / Session / Token  (2) 2023.12.07