서론
현재 REST API를 적용시키면서 데이터와 비즈니스 로직을 명확히 구분해 나가는 방식으로 개인 프로젝트를 진행하고 있다.
따라서 기존에는 회원가입에서 사용하던 Session과 Cookie를 이용한 인증 방식에서 JWT(JSON Web Token)을 사용하게 되었고, 이 용어들을 정리하고 개인 프로젝트를 진행하기로 하였다!
Cookie
등장배경
HTTP는 인터넷상에서 데이터를 주고받기 위한 서버/클라이언트 모델을 따르는 프로토콜이다.
- 클라이언트가 서버에게 요청을 보내면, 서버는 응답을 보냄으로써 데이터를 교환한다.
HTTP는 비연결성 및 무상태성이라는 특징을 가지고 있다.
- HTTP는 요청 처리 후 연결을 끊어버리기 때문에, 클라이언트의 상태 정보 및 현재 통신 상태가 남아있지 않다.
이 비연결성의 장점은 서버의 자원 낭비를 줄일 수 있다는 것이다.
하지만 비연결성은 우리가 로그인과 같은 일을 할 때 누가 로그인 중인지 클라이언트는 식별할 수 없다.
이와 같은 문제점을 해결하기 위해 Cookie와 Session이라는 기술을 활용한다.
Cookie란?
Java에서 cookie는 웹 애플리케이션에서 사용되는 중요한 요소이다.
쿠키는 클라이언트 측에서 상태 정보를 저장하기 위해 사용되며, 클라이언트 컴퓨터에 작은 텍스트 파일로 저장된다.
쿠키는 웹 페이지를 방문할 때마다 항상 서버로 전송된다.
주의점
- 쿠키 정보는 항상 서버에 전송되기 때문에 네트워크 트래픽이 추가적으로 발생한다.
- 최소한의 정보만 사용한다. 예) 세션 ID, 인증 토큰
- 보안에 민감한 데이터는 저장하면 안 된다.
- 주민번호, 신용카드 번호 등
쿠키의 용도
쿠키는 주로 세 가지 목적을 위해 사용된다.
- 세션 관리(Session management)
- 서버에 저장해야 할 사용자 로그인 정보를 받을 수 있다.
- 개인화(Personalization)
- 사용자 선호, 테마 등의 세팅할 수 있다.
- 트래킹(Tracking)
- 사용자 행동을 기록하고 분석하는 용도로 사용된다.
Set-Cookie & Cookie 동작방식
- Set-Cookie HTTP 응답 헤더는 서버로부터 사용자 에이전트로 전송된다.
- 간단한 쿠키는 다음과 같이 설정될 수 있다
Set-Cookie: key-value 형식으로 문자열로 담는다.
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: 01 Jul 2023 15:32:33 GMT;
서버는 클라이언트의 로그인 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 set-cookie에 담는다. 이후 클라이언트가 재요청 할 때마다 저장된 쿠키를 요청 헤더의 cookie 에 담아 보낸다. 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별할 수 있다.
쿠키의 생명주기
- setMaxAge();
- Set-Cookie: max-age=3600(3600초)
- 세션 쿠키: 만료 날짜를 생략하면 브라우저 종료 시까지만 유지
- 영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지
보안 문제 (단점)
- 쿠키만 사용해서 로그인을 유지할 때 발생할 수 있는 보안 문제가 있다
- 쿠키 값은 임의로 변경할 수 있다.
- 클라이언트가 쿠키를 강제로 변경하면 다른 사용자가 된다.
- 쿠키에 보관된 정보는 훔쳐갈 수 있다.
- 쿠키의 정보가 나의 로컬 PC가 털릴 수도 있고, 네트워크 전송 구간에서 털릴 수도 있다.
- 해커가 쿠키를 한번 훔쳐가면 평생 사용할 수 있다.
대안
- 쿠키에 중요한 값을 노출하지 않고, 사용자 별로 예측 불가능한 임의의 토큰(랜덤 값)을 노출하고, 서버에서 토큰과 사용자 id를 매핑해서 인식한다.
- 그리고 서버에서 토큰을 관리한다.
- 해커가 토큰을 털어가도 시간이 지나면 사용할 수 없도록 서버에서 해당 토큰의 만료시간을 짧게(예: 30분) 유지한다.
- 또는 해킹이 의심되는 경우 서버에서 해당 토큰을 강제로 제거한다.
Session
등장배경
웹 애플리케이션은 클라이언트의 요청에 대해 동적으로 페이지를 생성하고, 클라이언트의 상태를 유지해야 하는 경우가 많다.
예를 들어, 로그인 상태를 유지하면서 http로 개인 정보를 주고받다 보면 쿠키가 유출, 조작될 수 있는 큰 문제를 야기시킨다.
session은 인증 정보를 쿠키에 저장하지 않고 식별자를 사용하면서 상태 정보를 저장하기 위해 등장하였다.
Session이란?
웹 애플리케이션에서 클라이언트와 서버 간의 상태 정보를 유지하고 관리하기 위해 중요한 요소이다.
HTTP 프로토콜은 기본적으로 상태를 유지하지 않는 stateless 프로토콜이기 때문에, 세션은 클라이언트와 서버 간의 상태 정보를 추적하고 유지하기 위해 사용된다.
Session 활용 예시
사용자가 웹 애플리케이션에 접속할 때, 서버는 해당 사용자에 대한 고유한 세션을 생성한다.
이 세션에 관련된 데이터를 저장하기에 다음과 같이 사용될 수 있다.
- 로그인 정보 유지: 사용자의 로그인 상태를 추적하고, 인증된 사용자인지 확인
- 상태 정보 저장: 사용자의 장바구니, 선호 설정 등과 같은 상태 정보를 저장
- 데이터 공유: 서버의 여러 페이지나 요청 간에 데이터를 공유
Session 동작 방식
로그인을 하면 (ID, Password 정보를 전달하면) 서버에서 해당 사용자가 맞는지 확인
맞다면, 세션 ID(추정 불가능한 값)를 생성해 회원 정보를 저장
서버는 클라이언트에 mySessionId라는 이름으로 세션 ID만 쿠키에 담아서 전달한다.
클라이언트는 쿠키 저장소에 mySessionId 쿠키를 보관
서버에서는 클라이언트가 전달한 mySessionId 쿠키 정보로
세션 저장소를 조회해서 로그인 시 보관한 세션 정보를 사용
생명주기
세션은 일정 시간 동안 유지되며, 클라이언트의 비활동 시간이 일정 시간 이상 지속될 경우 만료된다. 세션의 생명주기는 다음과 같은 단계로 구성된다.
- 세션 생성: 클라이언트 접속 시 서버는 고유한 세션 ID를 생성하고, 클라이언트에게 전달
- 세션 유지: 클라이언트가 세션 ID를 서버에 전달하면, 서버는 해당 세션에 대한 상태 정보를 유지
- 세션 만료: 일정 시간 동안 클라이언트의 요청이 없을 경우 세션은 만료된다.
- 만료된 세션은 클라이언트의 요청 시 재생성
단점
- 서버 메모리 부하
세션은 서버 메모리에 저장되기 때문에, 많은 사용자가 동시에 접속하는 경우 서버의 메모리 부하가 증가할 수 있다.
- 확장성 문제:
세션은 단일 서버에 의존하기 때문에, 클러스터링이나 부하 분산을 위해 추가 구현이 필요하다.
- 데이터 손실 위험:
서버 장애 또는 재시작 시 세션 데이터의 손실이 발생할 수 있다.
- 보안 문제
세션 ID를 탈취하거나 세션 하이재킹(Session Hijacking)과 같은 공격으로부터 안전하지 않을 수 있다.
이러한 Stateless 세션 관리의 어려움과 분산 환경과 서비스 간의 연동하는 부분에서
권한관리의 어려움으로 JWT가 등장하게 된다.
JWT (JSON Web Token)
등장배경 및 장점
- 분산 환경
- JWT는 서버의 상태를 필요로 하지 않다.
- 마이크로서비스 아키텍처
- JWT는 토큰 기반으로 인증을 처리하기 때문에 각 서비스가 독립적으로 토큰을 검증할 수 있어 마이크로서비스 아키텍처에 적합하다.
- 클라이언트와의 상호 운용성
- JWT는 JSON 형식을 사용하고, Base64 인코딩을 통해 URL-safe 한 문자열로 표현된다. 이는 다양한 플랫폼과 언어에서 사용하기 쉽고, 클라이언트와 서버 간의 상호 운용성을 높인다.
- 클레임 기반 권한 제어
- JWT는 클레임(Claims)이라는 정보를 토큰에 포함시킬 수 있다.
- 토큰 자체에 사용자 정보와 권한 등을 포함시킬 수 있다.
- 권한 조회나 사용자 정보 조회를 최소화하여 효율적인 권한 제어를 할 수 있다.
- 무상태(Stateless)한 특성
- 서버의 부담을 줄여줄 뿐 아니라, API 서버의 확장성을 높여줍니다.
이러한 장점들은 RESTful api기반 로그인 로직을 간편하게 구현할 수 있다.
현재 jwt로 회원가입을 기능을 구현하였고, jwt에 대해서 정리해 보기로 하였다.
JWT란?
Json Web Token의 약자로 '웹에서 사용되는 Json 형식의 토큰'이다.
토큰에는 사용자의 권한 및 기본 정보, 서명 알고리즘 등이 포함되어 있다.
JWT는 한번 쓰인 원본 내용이 유지되었는지 확인할 수 있는 특징이 있기 때문에 이를 활용하여 변경이 되지 않았음을 보장할 수 있다는 장점이 있다.
또한 서버에 저장되지 않고 클라이언트에서 저장하기 때문에 서버의 메모리 부담을 덜 수 있다.
JWT를 인증 및 인가에 활용하는 이유.
- JWT는 원본 내용의 유지를 보장할 수 있다.
- 인증된 유저를 정의할 값과 인가에 활용될 권한을 유저에게 전송하는 데에 쓸 수 있고 이는 위변조가 불가능하다.
- 서버 내 Session에 로그인 인증 정보를 저장하고 sessionId를 Client에게 보내주는 방식과 다르게 JWT 자체에 인증, 인가와 관련된 정보를 저장할 수 있기 때문에 서버를 Stateless 하게 설계하는 데에 도움을 줄 수 있다는 큰 장점이 있다.
구체적인 JWT의 구조
기본적으로 JWT는 Header.Payload.Signature가 연달아 있는 구조로 각 부분의 온점(.)으로 구분되며,
이는 Base64 url를 통해 인코딩 되어 표현된다.
JWT의 내용은 인코딩 되는 것이지 모든 내용이 암호화되는 것이 아니기 때문에 누구나 읽을 수 있다는 생각을 가지고 민감한 정보는 절대 포함해서는 안된다.
위 사진은 jwt.io 사이트에서 인코딩 디코딩 데이터를 확인할 수 있는 사이트이다.
- Header에는 토큰의 유형과 서명 알고리즘, (빨간색)
- Payload에는 권한 및 기본 정보 (보라색)
- Signature에는 Header와 Payload를 Base64로 인코딩 (하늘색)
이후, Header에 명시된 해시 함수를 적용하고 개인키로 서명한 값(전자서명)이 담겨있다.
위 사이트처럼 토큰의 인가정보(payload)의 권한 정보를 임의로 바꾼다 해도
이 Signature를 통해 토큰에 대한 위변조 여부를 체크함으로써 위변조 된 토큰임을 확인할 수 있다.
AccessToken과 RefreshToken
JWT를 Access Token과 Refresh Token으로 분리하는 이유는 보안과 효율성 측면에서 이점을 제공하기 때문이다.
일반적으로 사용되는 인증 방식인 OAuth 2.0에서 사용되는 패턴 중 하나이며, 하나씩 살펴보자.
Access Token
- 클라이언트가 API 엔드포인트에 접근할 때 사용되는 토큰이다.
- Access Token은 짧은 유효 기간을 가지며, 클라이언트가 API 호출 시 사용된다
- Access Token은 보안적으로 취약한 환경인 네트워크를 통해 전송되므로, 유효 기간이 짧은 경우 탈취당하더라도 토큰이 길게 유지되지 않아 악용의 위험이 줄어들 수 있다.
RefreshToken
- Refresh Token은 Access Token의 만료 시점에 대한 갱신을 위해 사용되는 토큰이다.
- Refresh Token은 일반적으로 Access Token보다 더 긴 유효 기간을 가지며, 서버 측에 안전하게 저장된다.
- Refresh Token은 네트워크를 통해 전송되지 않기 때문에, 악의적인 공격자가 탈취하여도 토큰을 사용할 수 없다.
- 필요할 때마다 Refresh Token을 사용하여 새로운 Access Token을 발급받을 수 있다.
분리된 Access Token과 Refresh Token의 사용은 보안 측면에서 Access Token의 유효 기간을 제한하고, 효율성 측면에서 필요할 때마다 Refresh Token을 사용하여 새로운 Access Token을 발급받을 수 있도록 해준다.