위클리페이퍼

JWT로 인증 시스템을 구현하는 방법 및 RESTful API란?

불닭냠냠 2024. 11. 14. 09:18

React 애플리케이션에서 사용자 인증을 구현하는 대표적인 방법 중 하나가 JSON Web Token(JWT)을 활용하는 것입니다. JWT는 RESTful API와도 자연스럽게 호환되며, 서버에 상태를 저장하지 않고 인증할 수 있다는 장점이 있습니다. 


JWT 기반 인증의 기본 개념

JWT(JSON Web Token)는 JSON 포맷의 토큰으로, 세 가지 부분으로 구성되어 있습니다: 이전 글에서 잠깐 언급했었습니다!

  1. 헤더(Header): 어떤 알고리즘을 사용해 서명(Signature)했는지를 나타내는 정보입니다.
  2. 페이로드(Payload): 유저 정보나 권한 등의 데이터를 담고 있습니다.
  3. 서명(Signature): 서버에서 설정한 비밀 키와 Header, Payload로 생성된 해시로, 데이터의 위변조 여부를 확인하는 데 사용됩니다.

일반적으로 클라이언트에서 로그인을 시도하면 서버는 사용자에게 JWT를 발급해주고, 이후 모든 요청에 이 JWT를 함께 보내어 서버가 사용자를 인증할 수 있게 합니다. 클라이언트 측에서는 JWT를 localStorage 또는 sessionStorage와 같은 브라우저 저장소에 보관하게 됩니다.


React 애플리케이션에서 JWT 기반 인증 구현하기

1. 로그인 시 JWT 발급 및 저장

로그인을 시도한 사용자의 정보를 서버에 전달하고, 서버는 이 정보가 유효하면 JWT를 발급해 클라이언트에 반환합니다. React에서는 이 토큰을 localStorage 또는 sessionStorage에 저장하고, 이후 API 요청 시마다 HTTP 헤더에 이 토큰을 포함시켜 서버에 전달합니다. 저는 로컬스토리지에 저장하는 편입니다. 여기서 포인트는 setItem입니다!

async function login(username, password) {
  // 사용자 이름과 비밀번호를 사용해 서버에 로그인 요청을 보냄
  const response = await fetch('https://api.example.com/auth/login', {
    method: 'POST',  // POST 메서드로 서버에 로그인 데이터 전달
    headers: {
      'Content-Type': 'application/json',  // JSON 형식으로 데이터를 보낸다고 명시
    },
    body: JSON.stringify({ username, password }),  // username과 password를 JSON으로 변환해 전송
  });
  
  // 서버가 인증에 성공하면 JWT 토큰을 응답으로 받아옴
  if (response.ok) {
    const { token } = await response.json();  // 응답 JSON에서 JWT 토큰 추출
    localStorage.setItem('token', token);  // 받은 토큰을 localStorage에 저장해 이후 요청에서 사용
    return token;  // 로그인 성공 시 토큰 반환
  } else {
    throw new Error('Login failed');  // 로그인 실패 시 오류 발생
  }
}

 

해석을 주석으로 달아놓았습니다. 

2. API 요청 시 JWT를 포함하여 인증하기

fetch나 axios로 API 요청을 보낼 때마다 JWT를 Authorization 헤더에 포함시켜서 전송해야 합니다. 

여기서 포인트는 getItem입니다!

async function fetchUserData() {
  const token = localStorage.getItem('token');  // localStorage에서 JWT 토큰 가져옴
  const response = await fetch('https://api.example.com/user/data', {
    method: 'GET',  // GET 메서드를 사용해 데이터 요청
    headers: {
      'Authorization': `Bearer ${token}`,  // JWT를 Authorization 헤더에 포함해 서버에 전달
    },
  });
  const data = await response.json();  // 응답 데이터를 JSON으로 변환
  return data;  // 서버에서 받은 데이터 반환
}

 

해석을 주석으로 달아놓았습니다. 

3. JWT가 만료된 경우 처리

일반적으로 서버에서는 JWT에 만료 시간을 설정하고, 이 시간이 지나면 해당 토큰을 더 이상 유효하지 않다고 판단하게 됩니다. 따라서 API 호출을 할 때마다 토큰이 만료되었는지 확인하고, 만료된 경우 사용자에게 다시 로그인을 요청하는 로직을 추가해야 합니다.

이를 위해 토큰을 디코딩하여 exp(만료 시간) 필드를 확인하거나, 서버의 응답 상태 코드가 401 Unauthorized인 경우 재인증 절차를 시작하는 방식으로 구현할 수 있습니다.


로그아웃 로직 구현하기

로그아웃은 인증된 사용자의 세션을 종료하는 중요한 기능입니다. React 애플리케이션에서는 주로 클라이언트 측에서만 처리하는데, 로그아웃 버튼을 눌렀을 때 JWT를 삭제하는 식으로 간단하게 구현할 수 있습니다.

1. 클라이언트 측 로그아웃: 클라이언트에서 로그아웃할 때는 브라우저의 localStorage나 sessionStorage에 저장된 토큰을 삭제하여 서버와의 연결을 끊습니다. 여기서 포인트는 removeItem입니다!

function logout() {
  localStorage.removeItem('token');  // 저장된 토큰 삭제
  window.location.href = '/login';   // 로그인 페이지로 리다이렉트
}

 

 2. 서버 측 세션 관리 (선택 사항): 클라이언트 측에서 JWT를 삭제하는 것만으로 충분한 경우가 많지만, 보안이 더 필요한 경우에는 서버에서도 JWT를 무효화하는 방법을 고려할 수 있습니다. 예를 들어, JWT의 유효성을 저장하고 관리하는 서버의 캐시나 데이터베이스를 사용해 토큰을 블랙리스트에 올리는 방식으로 구현할 수 있습니다. 하지만 이 방식은 서버에 추가적인 복잡성과 리소스가 필요하기 때문에 상황에 맞게 선택하는 것이 좋습니다.

 


RESTful API란 무엇인가?

JWT 기반 인증이 주로 RESTful API와 함께 사용되기 때문에, 여기서 간단히 RESTful API에 대해서도 설명하겠습니다. REST(Representational State Transfer)는 분산 하이퍼미디어 시스템을 위한 아키텍처 스타일입니다. RESTful API는 이러한 REST 아키텍처 스타일을 기반으로 한 API를 의미합니다.

RESTful API가 갖추어야 할 주요 제약 조건은 다음과 같습니다:

 

1. 클라이언트-서버 구조

클라이언트와 서버가 분리되어야 하며, 클라이언트는 사용자 인터페이스를 담당하고 서버는 데이터와 로직을 제공하는 역할을 합니다. 이로 인해 개발이 독립적으로 가능해집니다.

2. 무상태성

서버는 클라이언트의 상태를 저장하지 않고, 각 요청이 독립적으로 이루어져야 합니다. 예를 들어, 클라이언트는 매 요청마다 필요한 인증 정보를 서버에 포함시켜 보내야 하며, 서버는 이전 요청의 상태를 기억하지 않습니다.

3. 캐시 가능성

RESTful 시스템은 클라이언트가 응답을 캐시할 수 있도록 해야 하며, 이로 인해 성능을 최적화할 수 있습니다. 서버는 각 응답에 캐시 가능한지 여부를 명시해야 합니다.

4. 계층화된 시스템

클라이언트는 중간 서버(프록시, 게이트웨이 등)를 통해 최종 서버에 접근할 수 있습니다. 이 계층화된 구조는 보안과 성능을 향상시키는 데 도움을 줍니다.

5. 인터페이스 일관성

리소스의 접근 및 조작을 위한 URI 디자인, HTTP 메서드의 일관된 사용 등이 필요합니다. 예를 들어, GET 메서드는 데이터를 조회하고 POST 메서드는 데이터를 생성하는 등의 역할을 고정적으로 갖습니다.

6. 코드 온 디맨드 (선택적 제약)

서버가 클라이언트에게 필요한 경우 코드(예: JavaScript)를 전송하여 실행할 수 있습니다. 이 제약 조건은 선택 사항으로, 필수는 아닙니다.


마치며

React 애플리케이션에서 JWT 기반의 인증 시스템을 구현할 때, 중요한 점은 클라이언트가 매 요청마다 올바른 토큰을 전달할 수 있도록 하고, 로그아웃 시 토큰을 안전하게 삭제하여 인증을 해제하는 것입니다. 특히, setItem, getItem, removeItem을 기억하면 좋을 것 같습니다. RESTful API의 제약 조건을 이해하면서 JWT 인증을 적용하면 보안성과 확장성을 동시에 잡을 수 있는 좋은 구조의 애플리케이션을 만들 수 있습니다. 곧 프로젝트를 진행하게 되는데 이 부분을 신경 쓰면서 도전하겠습니다!