데일리

[2023.11.24] 내가 놓치고 있던 부분을 발견하여 정리1 (cookie, session, JWT)

paikpaik 2023. 11. 24. 20:08

글의 목적

  • 프레임워크, 라이브러리, 도구, 함수 하나하나도 왜 선택해서 사용했는지 파악하기 위해 정리하려고 함.
  • 쿠키, 세션, Token, JWT, PM2, NGINX, gzip, VPC를 부트캠프에서 배운대로 사용했는데 생각해보니까 왜 써야하는지에 대해서는 잘 알고 있지 못했음.

본론

cookie

1. 쿠키가 왜 등장했고 필요한지?

  • HTTP는 웹에서 이루어지는 모든 데이터를 주고받기 위한 서버-클라이언트 모델을 따르는 프로토콜임.
  • HTTP는 connectionless와 stateless가 있는데 이 특징때문에 성능적인 측면에서는 매우 유용한 반면, 누가 보낸 요청인지를 기억하지 못함
  • 이러한 단점을 해결하기 위해 웹 브라우저는 쿠키라는 것을 사용함.
  • 사용자 컴퓨터에 저장됨.

2. 쿠키의 특징과 단점

  • 브라우저에 저장되며 브라우저 마다 쿠키가 저장되는 위치가 각각 다름.
  • 쿠키는 이름-값 형태로 정보를 문자열로 저장
  • 사용자의 웹 브라우저가 쿠키를 읽어서 데이터를 식별
  • 보유할 수 있는 데이터의 양이 매우 제한적임. 웹 사이트당(local Storage와 Session Storage는 5MB, 쿠키는 4KB)
  • 개발자도구로 쿠키를 읽거나 편집할 수 있어서 보안적인 가치는 매우 낮음.

3. 쿠키의 사용 방법 및 CRUD

  • 쿠키는 document.cookie로 CRUD가 가능함.
// 쿠키 생성
// 이름=값
document.cookie = "user=paikpaik";
// 여러개 쿠키 생성
// 쿠키는 접근자 속성으로 기존 쿠키를 덮어쓰지 않음.
document.cookie = "user1=paikpaik1";
document.cookie = "user2=paikpaik2";
document.cookie = "user3=paikpaik3";

// 쿠키 값 변경
// 이름을 기준으로 값을 변경할 수 있음.
document.cookie = "user3=paikpaik33";

// 쿠키 구성요소
// 쿠키는 이름과 값 이외에도 쿠키 만료 시간, 경로 등을 정의할 수 있음.
// 쿠키 값 뒤에 세미콜론(;)을 사용하여 쿠키 구성 요소를 설정함.
// expires: 쿠키 만료 날짜를 설정하며, 설정하지 않으면 브라우저를 닫을 때 쿠키가 만료됨.
// max-age: 쿠키가 생성된 시간부터 시작하여 만료되어야 하는 시간(초)을 설정함.
// path: 쿠키에 접근할 수 있는 절대 경로를 설정함.
// secure: 쿠키가 https를 통해서만 전송됨.
// samesite: 쿠키가 다른 웹 사이트에 전송되지 않도록 함.
const date = new Date();
date.setMinutes(date.getMinutes() + 60);
document.cookie = `user=paikpaik; expires=${date.toUTCString()}; secure`;

// 쿠기 값 읽기
// 쿠키 값을 가져오는 방법은 정규식을 활용하여 필요한 값만 가져옴.
const getCookieValue = (name) => (
  document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || ''
)
const value = getCookie("user");

// 쿠키 삭제
// 쿠키를 삭제하기 위해서는 쿠키의 구성 요소에서 expires를 오늘 이전으로 설정하거나 max-age를 0으로 설정함.
document.cookie = `user=paikpaik; max-age=${"0"}`;

google에 들어갔을 때 쿠키
naver에 들어갔을 때 쿠키

4. 쿠키의 활용

  • 뭐가 있을까 고민하다가 웹브라우저 접속시 뜨는 팝업이 쿠키를 활용한다고 해서 만들어봤다.
let popup = getCookie("eventpopup");
if (popup !== "false") {
    // 팝업 실행
    openPopup();
}

// 팝업 페이지
const openPopup = () => {
    // 팝업을 열기 위한 로직을 여기에 추가
}

const closeWindow = () => {
    const check = document.getElementById("check");
    if (check.checked) {
        // 쿠키 설정 또는 팝업을 닫기 위한 로직을 여기에 추가
        setCookie("eventpopup", "false", 1);
        window.close(); // 브라우저 창 닫기
    }
}

5. HTTP와 Set-Cookie, Cookie 헤더

  • HTTP 요청을 수신할 때, 서버는 응답과 함께 Set-Cookie 헤더를 전송할 수 있음.
  • 이 서버 헤더는 클라이언트에게 쿠키를 저장하라고 전달함.
TTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
  • 이제, 서버로 전송되는 모든 요청과 함께, 브라우저는 Cookie 헤더를 사용하여 서버로 이전에 저장했던 모든 쿠키들을 회신 함.
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

6. 쿠키 종류

Session Cookie 메모리에만 저장되며 브라우저 종료시 쿠키를 삭제.
Persistent Cookie 장기간 유지되는 쿠키(예를 들어 Max-Age 1년), 파일로 저장되어 브라우저 종료와 관계없이 사용.
Secure Cookie HTTPS에서만 사용, 쿠키 정보가 암호화 되어 전송.
Third-Party Cookie 방문한 도메인과 다른 도메인의 쿠키, 보통 광고 베너 등을 관리할 때 유입 경로를 추적하기 위해 사용.
  • Set-Cookie를 할때 Expires 속성이 존재하지 않으면 Session cookie로 간주하고 Expires 속성이 존재한다면 기간동안은 브라우저를 닫아도 사라지지 않는 persistent cookie로 간주한다.

session

1. 그럼 세션이란 무엇인가?

  • 세션이란 일정시간 동안 같은 사용자로 부터 들어오는 일련의 요구를 하나의 상태로 보고 그 상태를 일정하게 유지시키는 기술임.
  • 세션은 중요한 정보를 클라이언트에 저장하는 쿠키와는 다르게 서버에 저장하여 관리하기 때문에 사용자 정보가 노출되지 않음.
  • 서버에서는 클라이언트를 구별하기 위해 각각의 세션ID를 클라이언트마다 부여하게 되며 클라이언트가 종료되기 전까지 유지함.

2. 서버(세션) 기반 인증

  • 이전에 쿠키만을 사용하여 인증을 했을 경우에는 클라이언트에서 사용자 정보를 관리하여 보안에 매우 취약하다는 단점이 있어 요즘에는 로그인과 같이 보안상 중요한 작업을 할 때는 세션을 사용함.
  • 예를들어 사용자가 로그인을 하면, 세션에 사용자 정보를 저장해두고 서비스를 제공할 때 사용함.

출처 : 네트워크

  1. 사용자가 아이디와 비밀번호로 로그인을 한다.
  2. 서버 측에서는 해당 정보를 검증한다.
  3. 정보가 정확하다면 서버측에서 Set-Cookie를 통해 새로 발행한 SessionId를 보낸다.
  4. 클라이언트 요청 시 마다 서버에 저장된 세션Id와 클라이언트에 있는 sessionId가 일치 하는지 확인한다.
  • session을 통한 인증 방식은 소규모 시스템에서는 아직 많이 사용되고 있지만, 웹/앱 어플리케이션이 발달하게 되면서 서버 확장에 어려움이 생김. 

3. 서버 session기반 인증의 문제점

  • 서버 부하
    • 서버에서 클라이언트의 상태를 모두 유지하고 있어야 하므로, 클라이언트 수에 따른 메모리나 디스크 또는 DB에 부하가 심하다.
  • 확장성
    • 사용자가 늘어나게 되면 더 많은 트래픽을 처리하기 위해 서버를 확장해야 하는데 서버를 확장할때 세션이 저장되어 있는 서버로만 요청이 가도록 분산처리를 해줘야 하기 때문에 서버를 확장하기 어렵다.
  • CORS
    • 웹 브라우저에서 세션 관리에 사용하는 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어 CORS 방식(여러 도메인에 request를 보내는 브라우저)을 사용할 때 쿠키 및 세션 관리가 어렵다.

Token

1. 토큰 기반 인증

  • 토큰 기반 인증 시스템은 인증받은 사용자들에게 토큰을 발급하고, 서버에 요청을 할 때 헤더에 토큰을 함께 보내도록 하여 유효성 검사를 함.
  • 이러한 시스템에서는 더이상 사용자의 인증 정보를 서버나 세션에 유지하지 않고 클라이언트 측에서 들어오는 요청만으로 작업을 처리.
  • 즉, 서버 기반의 인증 시스템과 달리 상태를 유지하지 않으므로 stateless한 구조를 갖고 있음.

출처 : 네트워크

  1. 사용자가 아이디와 비밀번호로 로그인을 한다.
  2. 서버 측에서는 해당 정보를 검증한다.
  3. 정보가 정확하다면 서버측에서 사용자에게 토큰을 발급한다.
  4. 클라이언트 측에서 전달받은 토큰을 저장해두고, 서버에 요청을 할 때마다 해당 토큰을 서버에 함께 전달한다.
  5. 서버는 토큰을 검증하고, 요청에 응답한다.

2. 토큰 기반 인증 시스템의 이점

  • 무상태성 & 확장성
    • 토큰은 클라이언트 측에 저장되기 때문에 서버는 완전히 Stateless하며, 클라이언트와 서버의 연결고리가 없기 때문에 확장하기에 매우 적합하다.
  • 여러 플랫폼 및 도메인
    • 서버 기반 인증 시스템의 문제점 중 하나인 CORS를 해결할 수 있다. 토큰을 사용한다면 어떤 디바이스, 어떤 도메인에서도 토큰의 유효성 검사를 진행한 후에 요청을 처리할 수 있다
  • 최근에는 Json 포맷을 이용하는 JWT(Json Web Token)을 주로 사용함.
  • 하지만 이 또한 완벽하진 않음.

JWT

1. JWT란?

  • JWT(JSON Web Token)란 인증에 필요한 정보들을 암호화 시킨 토큰을 의미한다.

2. JWT 구조

  • JWT는 .을 구분자로 나누어지는 세 가지 문자열의 조합임.
  • Header

  • alg과 typ는 각각 정보를 암호화할 해싱 알고리즘 및 토큰의 타입을 지정한다.
  • Payload

  • Payload는 토큰에 담을 정보를 지니고 있다. Key-value 형식으로 이루어진 한 쌍의 정보를 Claim이라고 칭한다.
  • Signature

  • Signature는 인코딩된 Header와 Payload를 더한 뒤 비밀키로 해싱하여 생성한다.
  • Header와 Payload는 단순히 인코딩된 값이기 때문에 제 3자가 복호화 및 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화 할 수 없음.
  • 따라서 Signature는 토큰의 위변조 여부를 확인하는데 사용하고 서버는 환경변수로 따로 관리를 해서 유출을 막아야 함.

3. JWT의 장점과 단점

  • Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있음.
  • 인증 정보에 대한 별도의 저장소가 필요없음. (access토큰은 필요없지만 보안하기 위한 refresh 토큰은 db에 저장함.)
  • JWT는 토큰에 대한 기본정보와 전달할 정보 및 토큰이 검증됬음을 증명하는 서명 등 필요한 모든 정보를 자체적으로 지니고 있음.
  • 쿠키/세션과 다르게 JWT는 토큰의 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해짐.
  • Payload 자체는 암호화 되지 않기 때문에 유저의 중요한 정보는 담을 수 없음.
  • 토큰은 한번 발급 되면 유효기간이 만료될 때 까지 계속 사용되어 탈취 당하게 되면 대처가 불가능.

4. JWT 보안전략

  • 짧은 만료기한 설정
    • 토큰의 만료 시간을 짧게 설정하여 토큰이 탈취되더라도 빠르게 만료되기 때문에 피해를 최소화 할 수 있음.
    • 그러나 사용자가 자주 로그인 해야 하기 때문에 유저 경험적인 측면에서 매우 안좋음.
    • 근데 경험상 AWS는 거의 30분씩 재로그인을 요청하게 해서 그냥 사용하는것 같아보임. 
  • Refresh Token
    • 클라이언트가 로그인 요청을 보내면 서버는 Access Token과 그보다 긴 만료 기간을 가진 Refresh Token을 발급하는 전략임. (access 1시간 refresh 2주정도가 일반적)
    • 1. Access Token이 만료되었을 때 Refresh Token을 사용하여 Access Token의 재발급을 요청함.
    • 2. 서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 새로운 Access Token을 발급하고, 만료된 경우 사용자에게 로그인을 요구.
    • 3. 해당 전략을 사용하면 AccessToken의 만료 기한을 짧게 설정할 수 있으며, 사용자가 자주 로그인 할 필요도 없고 서버가 강제로 RefreshToken을 만료시킬 수 있음.
    • 4. 그러나 검증을 위해 서버는 RefreshToken을 별도로 저장하고 있어야 하므로 이는 JWT에 장점을 완벽하게 누릴 수 없다는 단점이 존재한다.

결론

  • 쿠키를 정리했는데 아직도 완벽하게 이해는 안가서 현재 진행하고 있는 인프런 프로젝트에서 프론트 분에게 쿠키를 사용하는 부분에서 시퀀스 다이어그램을 요청드려서 확인해보려고 함.
  • 세션방식의 장단점과 토큰방식의 장단점을 비교해봤을때 완벽한 보안은 없다는것이 결론이다.
  • session을 이용하면 보안적으로는 더 유리하다고 생각한다. 하지만 확장성을 고려한다면? 결국 session 서버로 분산처리를 해야하는데 그럴거면 토큰을 활용하는것을 고려해야하지 않을까?
  • 그렇다면 토큰을 했을때는 문제가 없느냐? 유저경험적인 측면에서 생각해보면 결국 문제가 발생한다.
  • 결국 refresh라는 대안으로 선택하게 되는데 그럼 결국 db에 저장을 해야하기 때문에 이럴거면 session쓰는게 좋지 않나? 라는 생각이 들게 된다. 
  • refresh를 보안해서 1회성의 로테이션방식도 있지만 이마저도 해커가 access만 계속해서 탈취한다거나 하는 방식은 막을수가 없다. 
  • 유저 경험과 서버의 환경 서비스의 방식등을 고려하려 완벽한 답을 찾기보다는 적절한 보안방식을 찾아가는게 좋지 않을까?!??

방법을 찾았다. - refresh & redis

  • refresh토큰을 공부하면서 왜 db에 저장할까? 그럴거면 뭣하러 토큰을 쓰나 싶었는데 이제 조금 알겠다.
  • jwt를 쓰는 이유는 토큰이고 시그니처로 자체 검증이 가능하기 때문이다. 즉,
  • 애초에 db저장의 목적이 어떤 user인지 파악하려는것이 아니라는 것.
  • 그럼 왜 저장하느냐? 저장을 하는 이유는 보안목적인데 정확하게 말하면 저장하지만 저장하지 않는것이다.
    1.  refresh토큰은 db에 저장하지 않고 redis에 저장한다.
    2. 저장하는 이유는 user 검증이 아닌 탈취당했을 경우 블랙리스팅으로 토큰 사용을 막으려는 것.
    3. 그렇기 때문에 휘발성이 강한 인메모리 방식의 redis에 저장하는 것.
    4. 그렇지만 이것 역시도 완벽하진 않을 것이다. 완벽한 보안은 없기 때문에.