들어가며
보통 서버가 클라이언트 인증을 확인하는 방식은 대표적으로 쿠키, 세션, 토큰 3가지 방식이 있다.
✅ Cookie
클라이언트(브라우저)에 저장되는 작은 데이터 조각 (key-value 형태)이다. 브라우저가 서버가 시키는대로 잠깐 들고다니는 메모장과 같은 개념이다.
Cookie 인증 방식

- 브라우저(클라이언트)가 서버에 요청을 보낸다.
- 서버는 클라이언트의 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보와 응답 헤더의 Set-Cookie에 담는다.
- 이후 브라우저는 이 값을 저장하고, 같은 도메인으로 요청할 때마다 자동으로 쿠키 헤더에 담아 서버로 보낸다.
- 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별한다.
특징
- 보안에 취약하다. 요청 시에 쿠키의 값을 그대로 보내기 때문에 유출 및 조작 당할 위험이 존재한다.
- 쿠키에는 용량 제한이 있어 많은 정보를 담을 수 없다.
- 웹 브라우저마다 쿠키에 대한 지원 형태가 다르기 때문에 브라우저 간 공유가 불가능하다.
- 쿠키의 사이즈가 커질수록 네트워크에 부하가 심해진다.
✅ Session
이러한 쿠키의 보안적인 이슈 때문에, 세션은 비밀번호 등 클라이언트의 민감한 인증 정보를 브라우저가 아닌 서버 측에서 저장하고 관리한다. 서버의 메모리에 저장하기도 하고, 서버의 로컬 파일이나 데이터베이스에 저장하기도 한다. 클라이언트는 단순히 세션 Id만 들고 다니고, 실제 민감한 정보는 클라이언트에 보내지 않고 서버에서 모두 관리한다는 점이다.
Session 인증 방식

- 유저가 웹 사이트에서 로그인을 하면 세션이 서버 메모리 혹은 데이터베이스 상에 저장된다. 이 때, 세션을 식별하기 위한 Session_Id 를 기준으로 정보를 저장한다.
- 서버에서 Session_Id를 브라우저의 쿠키(Set-Cookie)에 담아 응답을 한다.
- 브라우저는 Session_Id를 쿠키에 저장하고, 이후 요청 시마다 자동으로 포함하여 서버에 전송한다.
- 서버는 쿠키에 담긴 Session_Id를 확인하고, 세션 저장소에서 사용자 정보를 찾아 인증된 요청임을 확인한다.
특징
- 쿠키보다 보안성이 좋으며 서버에서 직접 관리하기 때문에 데이터 무결성이 유지 가능하다.
- 서버 메모리를 차지하므로 사용자가 많아질 수록 부담이 증가한다.
- 또한 서버 확장 시에 세션 동기화 문제가 발생할 수 있다.
- 쿠키를 포함한 요청이 외부에 노출되더라도 Session_Id 자체에는 유의미한 개인정보가 없다. 그러나, 탈취자가 Session_Id 자체를 탈취하여 클라이언트인 척 위장할 수 있다는 한계가 있다.
✅ 토큰
토큰 기반 인증 시스템은 클라이언트가 서버에 접속을 하면 서버에서 해당 클라이언트에게 인증되었다는 의미로 '토큰'을 부여한다. 토큰은 사용자가 인증되었음을 증명하기 위해 서버가 발급하는 유일한 문자열이다. 클라이언트는 로그인 시 발급받은 토큰을 가지고 이후 요청할 때마다 토큰을 함께 보내 인증을 대신한다. 토큰은 단순 문자열일 수도 있고, JWT 처럼 구조화된 데이터일 수도 있다.
토큰 인증 방식

- 사용자가 아이디와 비밀번호로 로그인을 요청한다.
- 서버가 사용자 정보를 확인하고 토큰을 발급한다.
- 클라이언트는 토큰을 로컬 저장소(localStorage, sessionStorage, 쿠키 등) 에 저장한다.
- 이후 요청마다 HTTP 헤더에 토큰을 포함해서 전송한다.
- 서버는 토큰의 유효성(위조 여부, 만료 시간 등)을 검증하고 요청을 처리한다.
특징
- 쿠키나 세션과 다르게 토큰 자체의 데이터 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해질 수 있다.
- Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.
- 토큰을 탈취당하면 대처하기 어렵다. 그렇기 때문에 사용 기간 제한을 설정하는 식으로 극복한다.)
✅ JWT (JSON Web Token)
JWT란 인증에 필요한 정보들을 암호화시킨 JSON 토큰을 의미한다. 그리고 JWT 기반 인증은 JWT 토큰 (Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별하는 방식이다. JWT는 JSON 데이터를 Base64 URL-safe-Encode를 통해 인코딩을 직렬화한 것이며, 토큰 내부에는 위변조 방지를 위한 개인키를 통한 전자서명도 들어가 있다. 따라서 사용자가 JWT를 서버로 전송하면 서버는 서명을 검증하는 과정을 거치게 되며, 검증이 완료되면 요청한 응답을 돌려준다.
구조
JWT는 `.`을 구분자로 나누어지는 3가지 문자열의 조합이다. `.`을 기준으로 좌측으로 헤더(Header), 내용(Payload), 서명(Signature)을 의미한다.

- Header에는 JWT에서 사용할 타입과 해시 알고리즘의 종류가 담겨있으며,
- Payload는 서버에서 첨부한 사용자 권한 정보와 데이터가 담겨있다
- 토큰에서 사용할 정보의 조각들인 Claim이 담겨있다. 서버와 클라이언트가 주고받는 시스템에서 실제로 사용될 정보에 대한 내용을 담고 있는 섹션이다.
- Claim : { "sub" : "123456789", "name" : "John Doe", "iat" : 1234567 }
- Signature에는 Header, Payload를 Base64 URL-safe Encode를 한 이후 Header에 명시된 해시함수를 적용하고, 개인키(Private Key)로 서명한 전자 서명이 담겨 있다.
- 시그니처에서 사용하는 알고리즘은 헤더(Header)에서 정의한 알고리즘 방식을 활용한다. (헤더 + 페이로드) 와 서버가 갖고 있는 유일한 key값을 합친 것을 헤더에서 정의한 알고리즘으로 암호화한다.
- 헤더와 페이로드는 단순히 인코딩된 값이기 때문에 제 3자가 복호화 및 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없다. 그래서 Signature는 토큰의 위변조 여부를 확인하는데 사용된다.
JWT 인증 방식

- 사용자가 아이디와 비밀번호를 입력하면 서버에 로그인 인증을 요청한다.
- 서버에서 클라이언트로부터 인증 요청을 받으면, Header, Payload, Signature를 정의한다. Header, Payload, Signature를 각 각 Base64로 한 번 더 암호화하여 JWT를 생성하고 이를 클라이언트에게 발급한다.
- 클라이언트는 서버로부터 받은 JWT를 로컬 스토리지에 저장한다.
- 그리고 API를 서버에 요청할 때 Authorization header에 Access Token을 담아서 보낸다.
- 서버는 클라이언트가 Header에 담아서 보낸 JWT가 내 서버에서 발행한 토큰인지 일치 여부를 확인하여 일치한다면 인증을 통과시켜주고 아니라면 통과시키지 않으면 된다.
- 클라이언트가 서버에 요청을 했는데, 만약 Access Token의 시간이 만료되면 클라이언트는 Refresh Token을 이용해서 서버로부터 새로운 엑세스 토큰을 발급 받는다.
특징
- Header와 Payload을 가지고 Signature을 생성하므로 데이터 위변조를 막을 수 있다.
- 세션처럼 서버가 상태를 저장할 필요가 없다.
- 사용자 인증에 필요한 정보(Claim)가 토큰에 들어 있으므로, 매 요청마다 DB 조회를 줄일 수 있으며 서버 부담이 감소된다.
- 토큰이 유출되면 만료되기 전까지 막을 방법이 없다.
- Header, Payload는 단순 Base64 인코딩이기 때문에 누구나 디코딩해서 내용을 확인할 수 있다. 따라서 민감 정보(비밀번호, 주민번호 등)은 절대로 넣으면 안된다.
JWT의 Access Token과 Refresh Token
이 JWT도 제 3자에게 토큰 탈취의 위험성이 존재하기 때문에, Access Token과 Refresh Token으로 이중으로 나누어 인증을 하는 방식을 택한다. 둘 다 같은 JWT이지만, 토큰이 어디에 저장되고 관리되느냐에 따른 사용 차이일 뿐이다.
- Access Token : 클라이언트가 갖고 있는 실제로 유저의 정보가 담긴 토큰으로, 클라이언트에서 요청이 오면 서버에서 해당 토큰에 있는 정보를 활용하여 사용자 정보에 맞게 응답을 진행한다.
- Refresh Token : 새로운 Access Token을 발급해주기 위해 사용하는 토큰으로 짧은 수명을 가지는 Access Token에게 새로운 토큰을 발급해주기 위해 사용한다.
'Spring' 카테고리의 다른 글
| [Spring] 레이어드 아키텍처(Layered Architecture)와 Spring MVC (0) | 2025.08.05 |
|---|---|
| [Spring] DispatcherServlet과 스프링부트 동작 구조 (3) | 2025.07.26 |
| [Spring] Spring Security의 구조와 흐름 (0) | 2025.07.17 |
| [Spring] 서블릿(Servlet)과 서블릿 컨테이너 (0) | 2025.07.16 |
| [Spring] 스프링 컨테이너와 빈 (0) | 2025.07.16 |
