JWT의 탄생
이전의 인증 체계
JWT 탄생 이전의 인증 체계는 Session & Cookie 방식을 사용했다.
이 방식에서의 여러 단점이 있었지만 가장 큰 단점은 세션 저장소였다. 사용자의 세션 ID를 모아놓은 세션 저장소를 관리해야 했다. 그리고 매 요청마다 세션 저장소에 접근해야 하는 비용이 컸다.
또한 Monolith 방식의 구조에서 프론트엔드와 백엔드의 분리가 일어나고, 더 나아가 백엔드가 여러 서버로 갈라지는 Microservice 구조가 나타났다. 이 과정에서 세션 방식은 너무 큰 비용을 발생시켰다.
서버가 따로 세션을 관리해서 HTTP의 무상태성을 위배시키는 방식이 아니라 새로운 인증 체계가 필요했다.
이것이 JWT이다.
새로운 인증 체계 - 토큰
그럼 이렇게 하기 위해서 어떤 흐름을 가져야 할지 생각해보자. 다음과 같이 "토큰"을 사용해볼 수 있다.
- 요청의 HTTP 헤더에 서버가 발급해준 증명서와 같은 토큰을 첨부해서 보낸다.
- 서버는 요청에 대해 옳은 응답을 해줄지, 토큰을 보고 판단한다.
- 토큰 인증에 성공하면 성공적인 응답을 보낸다.
이와 같은 흐름을 가능하게 하는 토큰이 필요했다. 여기서 JWT가 탄생한다.
JWT의 방식
결국 중요한 것은 서버가 토큰이 자신이 발급한 토큰인지를 인증할 수 있는 것이다. 그것을 위해 일반적인 문자열로 만든 토큰이 아닌, 문자열을 . (온점) 으로 3 부분으로 나눈 특별한 방식의 토큰이 나온다.
- Header는 해시 알고리즘과 토큰 타입을 정의한다. 토큰 타입은 JWT와 같은 타입을 말하고, 해시 알고리즘은 뒤의 Signature와 관련있다. JSON 형식의 평문을 BASE64로 인코딩한다.
- Payload는 실제 내가 사용자에게 줄 증명서에 적힌 사용자의 정보이다. 예를 들어, 서비스에서의 사용자의 ID 혹은 이름 같은 것들이다. JSON 형식의 평문을 BASE64로 인코딩한다.
참고 : BASE64 방식은 그저 ASCII 코드로 모두 변환한 문자열이지, 암호화를 위한 것이 아니다. 따라서 원문보다 인코딩한 문자열이 길이가 짧아지지 않는다. 오히려 길어진다. 또한, 인코딩한 문자열을 디코딩하는 것도 굉장히 쉽다. 즉, header, payload 값들은 JWT를 보고 원문을 알기 쉽다는 것이다.
- Signature는 앞의 header와 payload 부분 (예시에서의 aaaaaa.bbbbbb) 에다가 서버만의 시크릿 키 값을 해시 알고리즘으로 해싱한 값이다. 아래 사진을 참고하자.
쉽게...!
흐름을 한 장의 사진으로 정리해보았다.
토큰의 메타데이터들을 BASE64 Encoding 해놓고 (빨간색), 내가 넣고 싶은 데이터도 BASE64 Encoding 해놓는다. (파란색)
그리고 이 두 데이터와 나만의 시크릿 키를 합쳐 해시 함수에 넣은 Signature를 만든다. (보라색)
이제 세 문자열을 . (온점) 으로 잇는다. 위의 예에선 "eyJh~CJ9.eyJz~IyfQ.FuJB~hhoQ" 이런 식으로 JWT가 만들어질 것이다.
다시 한 번 인증 과정을 생각해보자. 서버는 클라이언트에게 JWT라는 증명서를 발급해줄 것이다. 이제 서버에 요청을 할 때마다 이 JWT를 같이 보내줄 것이다. 서버는 이 JWT가 어떻게 "내가 발급한 증명서" 라는 것을 알 수 있을까?
Signature 부분을 보면 된다. 온점을 기준으로 첫 번째, 두 번째 부분과 서버에서 지니고 있는 시크릿 키를 이용해 해시 함수를 적용시켜 보고, JWT 의 Signature 부분과 똑같은지 보면 된다.
다음의 경우(잘못된 JWT인 경우)를 모두 커버할 수 있다.
- 유저가 아닌 사람이 사용자가 사용하는 데이터 (Header, Payload) 를 모두 알게 됐다고 하더라도, 시크릿 키가 없으므로 JWT를 만들지 못한다.
- 유저가 악의적인 목적으로 Header 혹은 Payload를 바꾼 경우, 예를 들어서 exp(유효기간)를 바꿔서 JWT의 유효기간을 늘리게 되면, 서버에서 Signature 검증 시 올바르지 않은 JWT로 나온다.
JWT가 유효한지 확인하고 난 후, exp(유효기간)나 iat(발급시간)를 추가적으로 확인한 후 요청을 처리한다.
전체적인 흐름
로그인 (JWT 발급)
- 소셜 로그인 혹은 자체 로그인 등으로 로그인 요청을 보낸다.
- 아이디, 패스워드 등을 확인해 JWT를 응답해준다. 이 JWT를 해당 서버에 접근할 수 있는 토큰이란 의미로 Access Token 이라고 한다. 이 때, JWT를 어디에 담아서 보내줘야 할까...?? 그냥 Body에 보내줘도 될까? 아니면 뭔가 다른 곳에 보내줘야 할까? (답은 밑에!)
JWT 발급은 어디에?
다음 게시글을 통해 생각해볼 수 있다.
https://velog.io/@hiy7030/Spring-Security-JWT-Token
[Spring Security] JWT Token은 어디에 담아 발급해야 하는가
부트캠프 수료 후, 같은 부트캠프를 수료한 수강생 분들과 토이프로젝트를 진행하였다. 그 와중에 프론트 한 분께서 프리, 메인 프로젝트 때에도 JWT Token 발급 요청을 했을 때 늘 Response Header에 To
velog.io
정리하자면, 기술적으로 Header에 보내든 Body에 보내든 보안 등에서 문제될 것은 없다. 하지만 인증과 관련된 데이터라고 생각하니 Body보다는 Header에 보내는 게 맞는 것이라고 보통 생각한다.
But! 로그인 요청에서 실질적으로 원하는 응답의 데이터가 JWT 이므로, 이 경우에는 Body에 보내는 게 맞다는 것이다.
로그인 후 요청
- JWT를 요청에 함께 담아서 보내준다. 어떻게 담아서 보내줄까? (답은 밑에!)
- JWT의 Header, Payload, 시크릿 키를 이용해 Valid한지 확인하고, 유효기간 등을 모두 확인해서 옳은지 확인한다.
- 확인이 끝났으면 실제 요청에 대한 정상적인 응답을 보내준다.
JWT 사용은 어디에?
앞에서 말했듯, 실질적인 데이터를 Body에 보내는 게 맞다. JWT는 실질적인 데이터가 아니라 인증과 관련된 데이터이다. 따라서 이건 Body가 아니라 Header에 보내야 한다. 이 중에서 Authoriztion 필드에 보내야 한다.
이 때, Authorization 에 담아 보내는 토큰이 무엇인지 타입이 있다. 토큰은 JWT가 아니더라도 가능하기 때문이다.
Authorization 필드에는 다음과 같은 형식으로 데이터를 넣는다.
Authorization: <type> <credentials>
JWT가 아닌 토큰 중, Basic 타입의 경우 다음과 같이 적용된다.
Authorization: Basic base64({USERNAME}:{PASSWORD})
JWT는 Bearer 타입이다. 따라서 헤더에 적을 때 다음과 같이 적어야 한다.
Authorization: Bearer <token> // 이렇게 적혀야 한다.
Authorization: Bearer eyJhbGciO ~~~ adQssw5c //예시
Bearer 타입은 OAuth 2.0 에서 사용하는 타입이고, JWT도 이 타입을 사용한다고 보면 된다. 더 자세한 내용은 다음 게시글을 참고하자.
토근 기반 인증에서 bearer는 무엇일까?
본 글은 MDN - HTTP 인증, Veloport님의 게시글을 참고하여 작성되었습니다. 자세하게 알고싶으신 분은 해당 링크를 참고해주세요.토큰 기반 인증인증 타입마치며토큰 기반 인증은 쿠키나 세션을 이
velog.io
Basic 인증과 Bearer 인증의 모든 것
토스페이먼츠-HTTP 인증 HTTP 인증(Authorization)은 웹 서버의 비밀번호 같은 역할을 해줘요. 비밀번호로 이메일 계정의 권한을 확인하는 것 처럼, HTTP 인증으로 서버에 접근하는 클라이언트의 권한을
velog.io
JWT의 단점
다시 한 번 생각해보자. 서버는 클라이언트에게 JWT만 발급하고 나중에 이게 옳은지만 확인하면 되므로 서버에 다른 저장소가 필요없다. 로그인되어 있는 세션을 모두 저장해야 하는 세션 방식보다 훨씬 좋은 점이다.
하지만 단점도 물론 있다.
- JWT에서 쓰이는 BASE64는 쉽게 디코딩이 가능하기 때문에 남들에게 밝혀져도 좋을 최소한의 정보만 넣어야 한다. 서버에서만 사용하는 유저의 ID 라던가, 유효기간 같이 말이다.
- BASE64는 원래 문자열이 길어지면 인코딩해도 길어진다. 따라서 증명서에 적을 내용이 많다면 JWT도 길어지게 되고, 요청마다 전송할 HTTP 크기 자체가 커지게 된다.
- JWT를 누군가 알아냈을 경우, 유효기간 내에는 그 JWT를 막을 방법이 없다. 입장권을 뺏긴 것과 똑같다.
따라서 로그아웃하거나 이상이 생긴 JWT를 막는 블랙리스트를 따로 만들 수도 있다. 하지만 이러면,,, 세션 방식과 비슷하게 서버의 부담이 커진다.
절충안으로 Refresh Token을 생각해냈다. 이에 대해 다음 게시글에 설명하겠다. (Access Token과 Refresh Token)
다음 게시글
https://morenow.tistory.com/82
JWT 총정리 (2) - Access Token, Refresh Token
이전 게시글 https://morenow.tistory.com/56 JWT 총정리 (1) - JWT의 원리와 흐름 JWT의 탄생 이전의 인증 체계 JWT 탄생 이전의 인증 체계는 Session & Cookie 방식을 사용했다. 이 방식에서의 여러 단점이 있었지
morenow.tistory.com
'Spring Boot' 카테고리의 다른 글
Spring의 새로운 동기식 HTTP Client, RestClient (0) | 2024.03.09 |
---|---|
JWT 총정리 (2) - Access Token, Refresh Token (1) | 2024.03.08 |
Git in IntelliJ - 인텔리제이에서 Git의 모든 것 (2) (0) | 2023.02.03 |
Git in IntelliJ - 인텔리제이에서 Git의 모든 것 (1) (0) | 2023.01.27 |
[Spring Boot] Unit Test (단위 테스트) vs Integration Test (통합 테스트) (0) | 2023.01.20 |