JWT
JWT는 . 을 기준으로 좌측부터 Header.Payload.Signature의 의미를 가진
세가지 문자열의 조합이다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjE1LCJpYXQiOjE2NzQ0NjQwNzIsImV4cCI6MTY3NTY3MzY3Mn0.trE7PemoIJZ1y7u3F6hrURW3iHUCN1KBcPTsdVAueEw
Header에는 type과 hash알고리즘의 종류가 내포되어있고, Payload는 서버에서 응답한 response(권한 정보와 데이터 등)가 내포되어있다. Signature에는 Header와 Payload를 Base64 URL-safe Encode를 한 이후 Header에 명시된 해시함수를 적용하고, Private Key로 만들어진 전자서명이 내포되어 있다.
JWT에 대한 견해
나는 인증이 필요한 프로젝트를 만들었을때 JWT를 선택했다. 인증방식에 대한 많은 방법들이 존재하지만 주관적인 생각으로는 JWT의 데이터 변조에 대한 안정성은 타 인증방식에 비하면 검증된 방법이 생각했기 때문이었다. 토큰을 대조하여 인증하기 때문에 해당 데이터 위변조를 방지할 수 있다는 점 이 가장 컸고 또 다른 이점으로 해당 서비스가 아닌 다른 로그인 시스템에 접근, 권한공유가 가능한 구조이기 때문에 쿠키와는 차별화된 인증방식으로서 큰 이점을 가질 수 있다고 생각했기 때문이다.
Header
토큰의 유형과 서명 암호화 알고리즘을 내포하고 있다.
{
"alg": "HS256", // 서명화 알고리즘(HMAC SHA256, RSA 등)
"typ": "JWT" // 토큰의 유형
}
Payload
서버와 클라이언트가 실질적으로 주고받는 데이터를 담고 있는 섹션이다.
지정된 데이터 타입은 없지만 보편적으로 사용하는 데이터 타입으로
Registered claims, Public claims, Private claims 이 있다.
{
/* 위 예시와 동일코드 */
"sub": 15, // Registed Claim
"iat": 1674464072, // Registed Claim
"exp": 1675673672, // Registed Claim
/* 예시코드 */
"jti": 4200, // Registed Claim
"https://zeriong.tistory.com": true, // Public Claim
"useId": "zeriong@zeriong.com" // Private claim
}
1. Registed claims : predefined claims
- iss : issuer (발행자)
- exp : expiration time (만료시간)
- sub : subject (제목)
- iat : issued At (발행시간)
- jti : JWT ID
2. Public claims
- 사용자가 정의 가능한 클레임의 공개용 data를 전달할때 사용한다.
3. Private claims
- 인증이 완료된 당사자들 간에 정보를 공유하기 위해 만들어진 사용자 지정 클레임이다.
공개의 여부는 무관하지만 해당 유저를 특정할 수 있는 정보들을 내포한다.
Signature
시그니처는 헤더에서 정의한 알고리즘 방식인 alg를 활용한다.
(Header + Payload) + (Primary-Key in Server) 이렇게 둘을 합친 후
헤더에서 정의했던 알고리즘으로 암호화한다.
Signature = Base64Url( Header ) + . + Base64Url( Payload ) + server's Key\
JWT.IO에 접속해서 인코딩과 디코딩을 할 수 있다.
Access-Token, Refresh-Token을 통한 인증과정
- 클라이언트에서 ID,PW를 입력한다.
- 클라이언트에서 보낸 ID,PW가 DB에 정보와 매치되면 DB에 refresh-token을 저장하고
헤더를 통해서 refresh-token과 동일한 값을 가진 access-token을 보내준다. - 클라이언트에서 api를 요청할때 access-token을 같이 헤더에 실어 보내준다.
- 요청받은 access-token을 대조한 후 문제가 없을 경우에만 응답한다.
- 클라이언트에서 api를 요청 했는데 access-token이 만료된 경우에는
DB에 refresh-token를 이용해서 access-token을 재발급 해준다.
JWT는 보안?
결론부터 말하자면 JWT의 목적은 보안이 아닌 데이터 위조방지이다.
Base64를 통해서 암호화 하기 때문에 디버거를 사용하면 바로 복호화 할 수 있다.
그러므로 페이로드에 담기는 데이터는 모두 쉽게 노출될 수 있기 때문에 비밀번호나
민감한 정보는 담지 말아야 한다. 그리고 가장 처음 "암호화된 문자열" 이 아닌
"문자열의 조합" 이라 서술한 것도 이러한 특성 때문이었다.
JWT 장점
- Header와 Payload로 Signature를 생성하기 때문에 데이터 위변조를 방지할 수 있다.
- 인증 정보 저장에 대한 자유도가 높은 편에 속한다.
- data와 token이 검증 됨을 증명하는 서명과 필요한 모든 data를 자체적으로 지니고 있다.
- 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태(StateLess)가 되어 서버 확장성이 우수해질 수 있다.
- 쿠키와 다른 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능하다.
- OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있다.
- 모바일 어플리케이션 환경에서도 잘 동작한다. (모바일은 세션 사용 불가능)
JWT단점
- Self-contained의 형식으로 토큰 자체에 정보를 담고 있어, 양날의 검이 될 수 있다.
- 토큰의 Payload에 3종류의 클레임을 저장하기 때문에, 정보가 많아질수록 토큰의 길이가 늘어남에 따라 네트워크에 부하를 줄 수 있다.
- payload 자체는 암호화 된 것이 아니라 BASE64로 인코딩 된 것이기 때문에 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있으므로 payload에 중요 데이터를 넣지 말자.
- access-token하나만 가지고 운영을 할 경우 stateless 특징을 가진다. 즉 클라이언트 측에서 단독관리 를 하기 때문에 토큰 자체를 탈취당하면 대처하기가 어렵게 된다. 해결법으로 DB에서 refresh-token을 저장하여 access-token을 재발급 해주며 이중으로 보안을 강화할 수 있다.
마치며...
프론트개발자를 지향하며 공부하는 나로서 백엔드스택의 경험은 꽤나 복잡하고 어려운 구조였던 것 같다. 그래도 하나하나 풀어가며 이해하려 공을 들이다보니 어느새 많은 구조를 이해하게 된 것 같아 뿌듯 했다. 앞으로 어떤 인증방식이 유행을하고 더 보안이 강화가 된 인증방식이 나올지는 모르겠지만 아마 JWT방식은 앞으로도 오랜 사랑을 받을 것 같다.