일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- nestjs
- typescript
- java
- node.js
- 알고리즘
- Spring
- TIL
- css
- MySQL
- dfs
- OOP
- bean
- JWT
- Interceptor
- 탐욕법
- javascript
- 자료구조
- REST API
- LifeCycle
- Kubernetes
- Deep Dive
- winston
- 프로그래머스
- puppeteer
- 인접행렬
- 코딩테스트
- 인접리스트
- html
- GraphQL
- Linux
- Today
- Total
처음부터 차근차근
[JWT] JWT란? 본문
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는 각각의 구성요소가 점(.)으로 구분되어 있으며, 구성 요소는 다음과 같습니다.
- Header
- Payload
- Signature
1. Header
header에는 보통 토큰의 타입이나, 서명 생성에 어떤 알고리즘이 사용되었는지 저장합니다.
- alg : 서명 암호화 알고리즘(ex: HMAC SHA256, RSA)
- typ: 토큰 유형
2. 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
JWT에서 가장 중요한 것은 서명입니다.
이 Signature에는 Header의 인코딩 값과 Payload의 인코딩 값을 합친 후 이를 서버가 가지고 있는 개인키를 이용하여 암호화를 진행합니다.
따라서 Signature는 서버에 있는 개인키로만 암호화를 풀 수 있으므로 다른 클라이언트는 임의로 Signature를 복호화할 수 없습니다.
Signature는 JWT인증의 핵심입니다.
- JWT 토큰을 클아이언트가 서버로 요청과 동시에 전달합니다.
- 서버가 가지고 있는 개인키를 가지고 Signature를 복호화한 다음, Base64UrlEncode(header)가 JWT의 header와 일치하는지, base64UrlEncode(payload)가 일치한 지 확인하여 일치한다면 인증을 허용합니다.
만약 클라이언트가 payload에 담긴 식별자가 변조된 JWT로 요청을 하더라도 서버가 애초에 발급했던 Signature 안의 payload와 다르기 때문에 인증이 불가능해집니다.
- 다른 유저가 B를 임의로 수정 -> 유저 JWT: A.B'.C
- 수정한 토큰을 서버에 요청을 보내면 서버는 유효성 검사 시행
- 유저 JWT: A.B'.C
- C에 담겨져있는 B가 B'와 다르니 조작 확인
- 대조 결과가 일치하지 않아 유저의 정보가 임의로 조작되었음을 알 수 있다.
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, '번호로 서버가 켜졌어요!');
});
서버키를 입력하기 전 모습에도, Payload에는 User에 대한 정보가 담겨져 있습니다.
서버키를 입력한 후에, Header에는 암호화 알고리즘이 담겨져 있었으며,
Header와 Signature의 Base64Url Encode가 변경된 것을 알 수 있습니다.
JWT를 이용한 인증 과정
- 사용자가 ID, PW를 입력하여 서버에 로그인 인증을 요청한다.
- 서버에서 클라이언트로부터 인증 요청을 받으면, Header, PayLoad, Signature를 정의한다.Hedaer, PayLoad, Signature를 각각 Base64로 한 번 더 암호화하여 JWT를 생성하고 이를 쿠키에 담아 클라이언트에게 발급한다.
- 클라이언트는 서버로부터 받은 JWT를 로컬 스토리지에 저장한다. (쿠키나 다른 곳에 저장할 수도 있음)API를 서버에 요청할때 Authorization header에 Access Token을 담아서 보낸다.
- 서버가 할 일은 클라이언트가 Header에 담아서 보낸 JWT가 내 서버에서 발행한 토큰인지 일치 여부를 확인하여 일치한다면 인증을 통과시켜주고 아니라면 통과시키지 않으면 된다.인증이 통과되었으므로 페이로드에 들어있는 유저의 정보들을 select해서 클라이언트에 돌려준다.
- 클라이언트가 서버에 요청을 했는데, 만일 액세스 토큰의 시간이 만료되면 클라이언트는 리프래시 토큰을 이용해서
- 서버로부터 새로운 엑세스 토큰을 발급 받는다.
JWT의 장점
- Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있다.
- 인증 정보에 대한 별도의 저장소가 필요없다.
- JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증됬음을 증명하는 서명 등 필요한 모든 정보를 자체적으로 지니고 있다.
- 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태(Stateless)가 되어 서버 확장성이 우수해질 수 있다.
- 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능하다. (쿠키와 차이)
- OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있다.
- 모바일 어플리케이션 환경에서도 잘 동작한다. (모바일은 세션 사용 불가능)
JWT의 단점
- Self-contained : 토큰 자체에 정보를 담고 있으므로 양날의 검이 될 수 있다.
- 토큰 길이 : 토큰의 Payload에 3종류의 클레임을 저장하기 때문에, 정보가 많아질수록 토큰의 길이가 늘어나 네트워크에 부하를 줄 수 있다.
- Payload 인코딩 : payload 자체는 암호화 된 것이 아니라 BASE64로 인코딩 된 것이기 때문에, 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있으므로, payload에 중요 데이터를 넣지 않아야 한다.
- 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 |