데일리
[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"}`;
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. 서버(세션) 기반 인증
- 이전에 쿠키만을 사용하여 인증을 했을 경우에는 클라이언트에서 사용자 정보를 관리하여 보안에 매우 취약하다는 단점이 있어 요즘에는 로그인과 같이 보안상 중요한 작업을 할 때는 세션을 사용함.
- 예를들어 사용자가 로그인을 하면, 세션에 사용자 정보를 저장해두고 서비스를 제공할 때 사용함.
- 사용자가 아이디와 비밀번호로 로그인을 한다.
- 서버 측에서는 해당 정보를 검증한다.
- 정보가 정확하다면 서버측에서 Set-Cookie를 통해 새로 발행한 SessionId를 보낸다.
- 클라이언트 요청 시 마다 서버에 저장된 세션Id와 클라이언트에 있는 sessionId가 일치 하는지 확인한다.
- session을 통한 인증 방식은 소규모 시스템에서는 아직 많이 사용되고 있지만, 웹/앱 어플리케이션이 발달하게 되면서 서버 확장에 어려움이 생김.
3. 서버 session기반 인증의 문제점
- 서버 부하
- 서버에서 클라이언트의 상태를 모두 유지하고 있어야 하므로, 클라이언트 수에 따른 메모리나 디스크 또는 DB에 부하가 심하다.
- 확장성
- 사용자가 늘어나게 되면 더 많은 트래픽을 처리하기 위해 서버를 확장해야 하는데 서버를 확장할때 세션이 저장되어 있는 서버로만 요청이 가도록 분산처리를 해줘야 하기 때문에 서버를 확장하기 어렵다.
- CORS
- 웹 브라우저에서 세션 관리에 사용하는 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어 CORS 방식(여러 도메인에 request를 보내는 브라우저)을 사용할 때 쿠키 및 세션 관리가 어렵다.
Token
1. 토큰 기반 인증
- 토큰 기반 인증 시스템은 인증받은 사용자들에게 토큰을 발급하고, 서버에 요청을 할 때 헤더에 토큰을 함께 보내도록 하여 유효성 검사를 함.
- 이러한 시스템에서는 더이상 사용자의 인증 정보를 서버나 세션에 유지하지 않고 클라이언트 측에서 들어오는 요청만으로 작업을 처리.
- 즉, 서버 기반의 인증 시스템과 달리 상태를 유지하지 않으므로 stateless한 구조를 갖고 있음.
- 사용자가 아이디와 비밀번호로 로그인을 한다.
- 서버 측에서는 해당 정보를 검증한다.
- 정보가 정확하다면 서버측에서 사용자에게 토큰을 발급한다.
- 클라이언트 측에서 전달받은 토큰을 저장해두고, 서버에 요청을 할 때마다 해당 토큰을 서버에 함께 전달한다.
- 서버는 토큰을 검증하고, 요청에 응답한다.
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인지 파악하려는것이 아니라는 것.
- 그럼 왜 저장하느냐? 저장을 하는 이유는 보안목적인데 정확하게 말하면 저장하지만 저장하지 않는것이다.
- refresh토큰은 db에 저장하지 않고 redis에 저장한다.
- 저장하는 이유는 user 검증이 아닌 탈취당했을 경우 블랙리스팅으로 토큰 사용을 막으려는 것.
- 그렇기 때문에 휘발성이 강한 인메모리 방식의 redis에 저장하는 것.
- 그렇지만 이것 역시도 완벽하진 않을 것이다. 완벽한 보안은 없기 때문에.