HTTP(HyperText Transfer Protocol)는 웹에서 데이터를 주고받기 위해 사용되는 프로토콜이다.
HTTP는 기본적으로 무상태(stateless) 프로토콜로, 각 요청(Request)과 응답(Response)은 독립적으로 처리되며, 이전 요청과의 상태를 유지하지 않는다. 하지만 로그인 상태를 유지하거나 사용자의 장바구니 정보를 저장하는 등의 기능을 위해 사용자의 상태를 유지해야 할 필요가 있다. 이때 쿠키(Cookie)와 세션(Session)과 같은 기술이 사용된다.
1. 쿠키(Cookie)
쿠키는 클라이언트에 저장되는 작은 데이터 조각이다. 서버는 HTTP 응답 헤더에 Set-Cookie를 포함시켜 클라이언트에게 쿠키를 전달 하며, 클라이언트는 동일한 서버에 요청을 보낼 때마다 해당 쿠키를 HTTP 요청 헤더에 포함시켜 서버로 전송한다.
쿠키는 클라이언트의 브라우저에 저장되며 4KB 정도의 작은 크기로 제한된다. 만료 기간을 설정하여 만료 기간이 지나면 쿠키는 자동으로 삭제되며, 특정 도메인이나 경로에서만 사용되도록 설정할 수 있다.
보안을 위해 Secure 속성을 통해 HTTPS 연결에서만 전송되도록 설정할 수 있으며, HttpOnly 속성을 통해 JavaScript에서 접근할 수 없도록 설정할 수 있다.
쿠키는 클라이언트 측에 저장되기 XSS(Cross-Site Scripting) 공격 등으로부터 취약할 수 있다.
2. 세션(Session)
세션은 서버 측에서 사용자의 상태를 유지한다. 세션은 서버에 저장되며, 각 사용자마다 고유한 세션 ID가 발급됩는데, 이 세션 ID는 클라이언트에게 쿠키로 전달되며, 클라이언트는 이후 요청에서 이 세션 ID를 포함시켜 서버로 전송한다. 서버는 세션 ID를 통해 해당 사용자의 상태 정보를 식별하고 관리한다.
세션 ID만 클라이언트에게 전달되어, 실제 데이터는 서버에 안전하게 보관될 수 있고, 일정 시간 동안 활동이 없으면 만료되도록 설정할 수 있다. 또한 서버의 메모리나 DB를 사용하여 세션 데이터를 저장할 수 있기 때문에, 큰 데이터도 저장 가능하다.
세션의 동작 방식
- 사용자가 로그인하면 서버는 세션을 생성하고 세션 ID를 발급
- 서버는 이 세션 ID를 클라이언트에게 쿠키로 전달
- 클라이언트는 이후 요청에서 이 세션 ID를 포함시켜 서버로 전송
- 서버는 세션 ID를 통해 해당 사용자의 상태 정보를 확인하고 요청을 처리
- 사용자가 로그아웃하거나 세션이 만료되면 서버는 세션 데이터를 삭제
세션을 활용하여 사용자의 인승 상태를 유지하여 로그인 상태를 유지하거나 장바구니 정보를 저장할 수 있다.
세션은 서버에 저장되기 때문에, 많은 사용자가 접속 시 서버의 메모리나 데이터베이스에 부하가 발생할 수 있고, 서버 확장 시 세션 데이터 공유를 위한 추가적인 메커니즘이 필요하다.
위의 개념을 활용하여 Spring Security와 React를 사용하여 로그인을 구현하는 방법이 여러 가지가 있다.
1. 세션 기반 인증 (Session-Based Authentication)
이 방법은 전통적인 웹 애플리케이션에서 많이 사용되는 방식입니다. Spring Security는 세션을 사용하여 사용자의 인증 상태를 관리한다.
구현 방법
- React에서 사용자가 로그인 폼을 통해 아이디와 비밀번호를 입력
- Spring에서 로그인 요청을 처리하고, 인증이 성공하면 세션을 생성하고 세션 ID를 쿠키로 클라이언트에게 전달
- 이후 요청에서는 React는 쿠키에 포함된 세션 ID를 자동으로 서버에 전송
- 서버는 세션 ID를 통해 사용자의 인증 상태를 확인하고 요청을 처리합니다.
이러한 방법으로 구현 시 구현이 간단하고 Spring Security가 자동으로 세션을 관리해준다. 그러나 세션 데이터로 인한 서버 부하, 확장에 대한 추가적인 구현이 필요하며 CSRF 공격에 취약하여 CSRF 토큰을 통해 추가적인 구현이 필요하다.
2. JWT(JSON Web Token) 기반 인증
JWT는 클라이언트 측에 토큰을 저장하고, 각 요청마다 토큰을 서버에 전송하여 인증을 수행하는 방식이다.
구현 방법
- React에서 사용자가 로그인 폼을 통해 아이디와 비밀번호를 입력합니다.
- Spring에서 로그인 요청을 처리하고, 인증이 성공하면 JWT를 생성하여 클라이언트에게 반환한다.
- React는 JWT를 로컬 스토리지나 쿠키에 저장 후, 요청마다 HTTP 헤더에 토큰을 포함시켜 서버에 전송한다.
- 서버는 헤더에 포함된 JWT를 검증하여 사용자의 인증 상태를 확인하고 요청을 처리한다.
서버는 세션을 유지할 필요가 없기 때문에 확장성이 뛰어나고 JWT가 클라이언트 측에 저장되기 때문에 서버의 부하를 줄일 수 있다. JWT는 모바일 앱이나 다른 클라이언트에서도 쉽게 사용할 수 있다.
단점은 JWT가 Base64로 인코딩된 문자열이기 때문에, 큰 데이터를 저장할 경우 토큰의 크기가 커질 수 있으며 로컬 스토리지에 저장될 경우 XSS(Cross-Site Scripting) 공격에 취약할 수 있다. 이를 방지하기 위해 HttpOnly 쿠키에 저장할 수 있지만, 이 경우 CSRF 공격에 취약해질 수 있다. JWT는 한 번 발급 시 서버에서 강제로 만료시킬 수 없어서 토큰 만료 시간을 짧게 설정하고 리프레시 토큰을 사용하는 등의 추가적인 구현이 필요하다.
3. OAuth2 + JWT 기반 인증
OAuth2는 외부 서비스(예: Google, Kakao, Naver 등)를 통해 인증을 수행하는 방식이다. JWT와 결합하여 사용할 수 있다.
구현 방법
- React에서 사용자에게 Google 또는 Facebook과 같은 OAuth2 제공자로 로그인합니다.
- Naver, Google과 같은 OAuth2가 사용자를 인증하고, 인증 코드를 클라이언트에게 반환한다.
- Spring은 인증 코드를 사용하여 액세스 토큰을 요청하고, JWT를 생성하여 클라이언트에게 반환한다.
- React에서 JWT를 로컬 스토리지나 쿠키에 저장 후 요청마다 HTTP 헤더에 토큰을 포함시켜 서버에 전송한다.
- 서버에서 이를 검증하여 사용자의 인증 상태를 확인하고 요청을 처리한다.
이를 이용하면 구현이 복잡하고 여러 단계를 거쳐야하며, 외부 서비스에 의존하여 해당 서비스가 다운되면 로그인이 불가능할 수 있지만, 사용자는 별도의 회원가입 없이 쉽게 외부 서비스로 로그인할 수 있으며, OAuth2는 보안성이 뛰어난 프로토콜이며 JWT를 사용하여 무상태 인증을 구현할 수 있다.
4. 리프레시 토큰(Refresh Token)을 사용한 JWT 인증
JWT의 단점을 보완하기 위해 리프레시 토큰을 사용하는 방법이다. 액세스 토큰은 짧은 유효 기간을 가지고, 리프레시 토큰은 긴 유효 기간을 가진다.
위 방법은 로그인 요청 처리 후, 액세스 토큰과 리프레시 토큰을 생성하여 클라이언트에 반환 후 클라이언트가 액세스 토큰을 로컬 스토리지나 쿠키에 저장하고, 리프레시 토큰은 안전한 저장소에 저장 후, 액세스 토큰 만료 시 리프레시 토큰을 사용하여 새로운 액세스 토큰을 서버에 요청한다.
유효 기간이 짧은 액세스 토큰으로 인해 토큰이 유출되더라도 피해를 최소화하며 리프레시 토큰을 통해 지속적 인증이 가능하다.
각 방법은 장단점이 있으므로, 애플리케이션의 요구사항과 보안 요구를 고려하여 적절한 방법을 선택하는 것이 중요하다.
JWT는 무상태(stateless) 인증 방식으로, 클라이언트와 서버 간의 인증을 간단하고 효율적으로 처리할 수 있다. 특히 클라이언트 측에서 토큰을 관리하기 때문에 단일 페이지 애플리케이션(SPA)에서 많이 사용되며, 세션을 유지하기 어려운 모바일 앱에서 많이 사용된다. JWT의 단점을 보완하기 위해 리프레시 토큰을 사용하는 방식을 사용하기도 한다.
OAuth2는 외부 서비스(예: Google, Facebook, GitHub)를 이용하여 별도의 회원가입 없이 Google, Facebook 등의 계정으로 로그인할 수 있어 사용자의 편의성이 높고 회원가입 절차를 간소화할 수 있어 많이 사용된다. 또한 OAuth2의 보안성과 JWT를 사용한 무상태 인증으로 보안성이 뛰어나다.
'공부' 카테고리의 다른 글
JWT란? (0) | 2025.02.05 |
---|---|
웹 서버(Web Server) - React + nginx (0) | 2025.01.20 |