[Spring Security] JWT로 로그인/로그아웃 구현하기

Ogu·2023년 9월 17일
0
post-thumbnail

이번 시간에는 토큰 기반 인증의 개념을 알아보고, JWT를 활용해 로그인/로그아웃을 구현해 보겠습니다.

토큰 기반 인증

HTTP의 비상태성 (Stateless)

HTTP는 WWW(World Wide Web) 상에서 서버/클라이언트 사이의 요청과 응답 데이터를 주고받기 위한 프로토콜입니다. 이러한 HTTP는 비상태성, 비연결성의 특징을 가집니다.

  • 비연결성(Stateless) : 서버가 클라이언트의 상태를 보존하지 않음

  • 비연결성(Connectionless) : 실제로 요청을 주고 받을 때만 연결을 유지하고, 응답을 주고 나면 TIP/IP 연결을 끊음. -> 최소한의 자원으로 서버 유지

따라서 HTTP는 직전에 발생한 통신을 기억하지 못하기 때문에 이전에 클라이언트가 인증을 거쳤는지 알 수 없습니다.

하지만 매 요청마다 사용자에게 로그인을 요구하는 것은 비효율적이고 불편할 것입니다.

이러한 문제를 해결하기 위해 웹 어플리케이션은 세션&쿠키 또는 토큰을 사용해 인가 과정을 거칩니다.

세션 기반 인증과 토큰 기반 인증

앞서 본 HTTP의 stateless한 특성으로 인해 각 통신의 상태가 저장되지 않기 때문에, 웹사이트에서는 인증을 관리하기 위한 방법들이 필요했습니다.

세션 기반 인증

세션 기반 인증에는 세션(Session)쿠키(Cookie) 가 사용됩니다.
세션 기반 인증 방식은 인증 정보를 서버에 저장하는 방식으로, 과정은 다음과 같습니다.

  • 클라이언트가 아이디와 비밀번호를 서버에 전달해 인증을 요청합니다.
  • 서버는 아이디와 비밀번호를 DB에서 조회해 사용자를 확인하고 해당 해원 정보로 세션을 생성하여 서버의 세션 저장소에 저장합니다.
  • 사용자에게 저장된 세션 정보의 Session ID(세션 식별자)를 발급합니다.
  • Session ID는 브라우저에 쿠키 형태로 저장되고, 실제 인증 정보는 서버에 저장됩니다.
  • 인증 절차 이후의 요청마다 브라우저는 HTTP Cokkie의 헤더에 Session ID를 담아 서버에 보냅니다.
  • 서버는 요청을 전달받고, Session ID에 해당하는 세션 정보를 세션 저장소에서 찾습니다.
  • 존재한다면 쿠키 검증을 마치고 해당 사용자를 인증된 사용자로 판단하여 클라이언트에 요청 데이터와 함께 응답을 보냅니다.

토큰 기반 인증

세션 기반 인증이 인증 정보를 서버에 저장하는 것과 달리, 토큰 기반 인증은 인증 정보를 클라이언트가 직접 들고 있다가, 여러 요청을 이 토큰과 함께 신청하는 방식입니다.
토큰은 요청과 응답에 함께 보내며 서버는 토큰만 보고 유효한 사용자인지 검증합니다.

그 과정은 아래와 같습니다.

  • 클라이언트가 아이디와 비밀번호를 서버에 전달해 인증을 요청합니다.
  • 서버는 아이디와 비밀번호를 DB에서 조회하고, 유효한 사용자인지 검증하고, 유효한 사용자라면 토큰을 생성해서 응답합니다.
  • 클라이언트는 서버에서 준 토큰을 저장합니다.
  • 이후 인증이 필요한 API 요청시 토큰을 HTTP의 Authorization 헤더에 담아 서버에 보냅니다.
  • 서버는 토큰이 유효한지(위변조되었거나, 만료된 토큰인지) 검증을 하고, 유효하다면 클라이언트가 요청한 내용을 처리해 응답을 보냅니다.

토큰 기반 인증의 특징

🍑 무상태성

사용자의 인증 정보가 담겨 있는 토큰은 클라이언트에서 가지고 있으므로 서버에 저장하고 있을 필요가 없습니다. 따라서 클라이언트에서 사용자의 인증 상태를 유지하며 요청을 처리하는데, 이것을 상태관리라고 합니다.
이 방식은 서버가 사용자의 인증 정보를 저장하거나 유지할 필요가 없기 때문에 완전한 무상태(Stateless)로 효율적인 검증을 할 수 있습니다.

🍑 확장성

최근 모던 웹에서 토큰 기반 인증을 사용하는 가장 큰 이유입니다.
일반적으로, 웹앱은 서버 확장을 위해 여러 대의 서버를 추가하는 수평 확장 방식을 사용합니다. 하지만 별도의 작업을 하지 않는다면, 세션 불일치 문제가 발생하여 Stiky Session, SessionClustering 등의 작업이 필요하죠.

토큰은 이러한 세션 불일치 문제로부터 자유롭습니다.

세션 인증 기반은 각 API에서 인증을 해야하지만, 토큰 기반 인증에서는 토큰을 클라이언트가 들고있기 때문에, 하나의 토큰으로 여러 서버에 요청을 보낼 수 있습니다.

또한 구글, 카카오 로그인과 같은 토큰 기반 인증의 다른 시스템에 접근해 로그인 방식을 확장하고, 다른 서비스에 권한을 공유할 수도 있습니다.

🍑 무결성

토큰 방식은 HMAC(Hashed-Based Message Authentication) 라고도 부릅니다.
토큰을 발급한 이후에 토큰 정보를 변경할 수 없어 무결성이 보장됩니다. 즉, 데이터의 위조를 보장해주어, 한글자라도 변경되었다면 유호하지 않은 토큰이라고 판단합니다.

JWT

JWT(JSON Web Token)는 인증에 필요한 정보들을 암호화시킨 JSON 토큰입니다.
JWT 기반 인증은 발급받은 JWT를 HTTP 요청 헤더의 Authorization 키값에 Bearer + JWT 토큰 값 을 넣어 보내 서버가 클라이언트를 식별하는 방식입니다.

JWT는 JSON 데이터를 Base64-safe Encode를 통해 인코딩해 직렬화한 것으로, 토큰 내부에는 위변조 방식을 위한 개인키를 통한 전자 서명이 있습니다.

JWT의 구조

JWT는 .을 기준으로 헤더(header), 내용(payload), 서명(signature)으로 이루어져 있습니다.

🌠 JWT Debugger
다음의 JWT 공식 사이트에서 JWT를 쉽게 생성해볼 수 있습니다.
https://jwt.io/

📢 Header - 헤더

Header에는 토큰의 타입과 해싱 알고리즘을 지정하는 정보를 담습니다.
디코딩된 JWT를 보면 헤더의 모습은 다음과 같습니다.

📢 Payload - 내용

내용에는 토큰과 관련된 정보를 담습니다. 이곳에 포함된 속성들은 클레임(Claim)이라고 하며, 크게 세가지로 분류됩니다.

  • 등록된 클레임(Registered Claims)
  • 공개 클레임(Public Claims)
  • 비공개 클레임(Private Claims)

⭐ 등록된 클레임

등록된 클레임은 필수는 아니지만 토큰에 정보를 담기 위해 이미 일므이 정해져 있는 클레임입니다. 정의된 클레임은 다음과 같습니다.

  • iss : JWT의 발급자(issuer) 주체를 나타냅니다. iss의 값은 문자열이나 URI를 포함하는 대소문자를 구분하는 문자열입니다.
  • sub : JWT의 토큰 제목(Subject)입니다.
  • aud : JWT의 수신인, 즉 토큰 대상자(Audience)입니다. JWT를 처리하려는 각 주체는 해당 값으로 자신을 식별해야 하며, 요청을 처리하는 주체가 'aud'값으로 자신을 식별하지 않으면 JWT는 거부됩니다.
  • exp : JWT의 만료시간(Expiration)입니다. 시간은 NumericDate형식으로 지정하며, 항상 현재 시간 이후로 설정합니다.
  • nbf : Not Before.의 약자로, 토큰의 활성 날짜와 비슷한 개념입니다. NumericDate형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지 토큰이 처리되지 않습니다.
  • iat : JWT가 발급도ㅚㄴ 시간(Issued at)입니다.
  • jti : JWT의 고유 식별자(JWT ID)입니다. 주로 일회용 토큰이나 중복처리를 방지하기 위해 사용됩니다.

⭐ 공개 클레임

공개 클레임은 공개되어도 상관 없는 클레임으로, 키 값을 마음대로 정의할 수 있는 사용자 정의 클레임입니다. 충돌을 방지할 수 있는 이름으로 설정행야 하며, 보통 클레임 이름을 URI로 짓습니다.

⭐ 비공개 클레임

공개되면 안 되는 클레임으로, 클라이언트와 서버 간의 상호 합의된 통신에 사용됩니다.

📢 Signature - 서명

서명은 해당 토큰이 조작되었거나 변경되지 않았음을 확인하는 용도로 사용합니다.
헤뎌의 인코딩값과 내용의 인코딩값을 합친 후에, 비밀키와 알고리즘 속성값을 가져와 해시값을 생성합니다.

참고 URL

리프레시 토큰과 JWT를 이용한 인증 과정

만약 토큰의 유효기간이 하루라면, 그 하루동안은 해당 토큰으로 무엇이든 할 수 있을 것이므로, 보안에 취약할 수 있습니다. 하지만 토큰의 유효 기간이 너무 짧다면, 사용자 입장에서는 짧은 시간동안만 활용할 수 있으니 불편할 것입니다. 이러한 불편함을 해결하기 위해 액세스 토큰의 유효기간은 짧게 하되, 리프레시 토큰을 사용합니다.
리프레시 토큰은 사용자를 인증하기 위한 용도인 액세스 토큰과 달리, 액세스 토큰이 만료되었을 때 새로운 액세스 토큰을 발급하기 위해 사용합니다.
따라서 액세스 토큰의 유효 기간은 짧게, 리프레시 토큰의 유효 기간은 길게 설정하여, 공격자가 액세스 토큰을 탈취해도 몇 분 뒤에는 사용할 수 없는 토큰이 됩니다.

  • Access Token : 접근
  • Refresh Token : 재발급

📢 Access / Refresh Token 재발급 원리

  • case1 : access token과 refresh token 모두가 만료된 경우 → 에러 발생 (재 로그인하여 둘다 새로 발급)
  • case2 : access token은 만료됐지만, refresh token은 유효한 경우 → refresh token을 검증하여 access token 재발급
  • case3 : access token은 유효하지만, refresh token은 만료된 경우 → access token을 검증하여 refresh token 재발급
  • case4 : access token과 refresh token 모두가 유효한 경우 → 정상 처리

📢 JWT를 이용한 인증 과정

  1. 사용자가 ID , PW를 통해 로그인 및 인증 요청.

  2. 서버에서는 회원 DB에서 값을 비교

3~4. 로그인이 완료되면 Access Token, Refresh Token을 발급한다. 이때 회원DB에도 Refresh Token을 저장해둔다.

  1. 사용자는 Refresh Token은 안전한 저장소에 저장 후, Access Token을 헤더에 실어 요청을 보낸다.

6~7. Access Token을 검증하여 이에 맞는 데이터를 보낸다.

  1. Access Token 만료

  2. 사용자는 이전과 동일하게 Access Token을 헤더에 실어 요청을 보낸다.

10~11. 서버는 Access Token이 만료됨을 확인하고 권한없음을 신호로 보낸다.

💡 Payload를 통해 클라이언트에서 유효기간을 알 수 있음!
Access Token 만료가 될 때마다 계속 과정 9~11을 거칠 필요는 없다.
사용자(프론트엔드)에서 Access Token의 Payload를 통해 유효기간을 알 수 있다.
따라서 프론트엔드 단에서 API 요청 전에 토큰이 만료됐다면 곧바로 재발급 요청을 할 수도 있다.

  1. 사용자는 Refresh Token과 Access Token을 함께 서버로 보내 액세스 토큰 발급을 요청한다.

  2. 서버는 받은 Access Token이 조작되지 않았는지 확인한후, Refresh Token과 사용자의 DB에 저장되어 있던 Refresh Token을 비교한다. Token이 동일하고 유효기간도 지나지 않았다면 새로운 Access Token을 발급해준다.

  3. 서버는 새로운 Access Token을 헤더에 실어 다시 API 요청 응답을 진행한다.



참고

profile
私はゲームと日本が好きなBackend Developer志望生のOguです🐤🐤

0개의 댓글