대부분의 글은 HTTP Cookies Crash Course 를 보고 작성했습니다
HTTP
는 Stateless
기본적으로 웹 통신에 사용하는 프로토콜인 HTTP
는 stateless
를 지향한다.
단순히 클라이언트와 서버 간 통신에서
어떤 정보를 , 어디로 전송할지에 대한 정보만 가지고 통신을 주고 받으며
단순히 요청 성공 , 요청 실패 등에 대한 응답과 함께 정보를 주고 받는다.
이렇게 요청 시 마다 클라이언트가 어떤 상태였는지와 상관 없이
그저 요청에 필요한 정보만 header
에 담아 요청을 주고 받는 단순한 구조 덕분에
유지보수가 쉽고 직관적인 통신이 가능했다.
하지만 우리는 종종 stateless
한 통신이 아니라 stateful
한 통신을 원할 때가 있다.
stateful
하게 통신 하고 싶은 상황가장 먼저 생각나는 stateful
한 상황은 로그인과 관련된 상황일 것이다.
예를 들어 어떤 페이지에서 회원과 비회원에게 보이는 화면이 다르다고 가정해보자
이 때 여전히 통신을 stateless
하게 해둔다면
클라이언트는 회원에게 보이는 화면을 보기 위해서는 항상
서버와 통신을 주고 받을 때 아이디와 비밀번호를 입력해야 할 것이다.
요청을 주고 받는 순간 아이디와 비밀번호를
header
에 적어야 하기 떄문이다.
혹은 인터넷 쇼핑을 할 때 장바구니에 물품들을 모두 담은 후 미처 못담은 물품이 있어
뒤로 가기 버튼을 눌렀다가 다시 장바구니를 들어갔을 때
여태 담아뒀던 물품들이 모두 사라져있다면 사고싶은 마음이 쏙 사라질 것이다.
state
를 기억해둘 수 있을까이를 해결하기 위해 브라우저는 사용자의 state
를 기억 할 수 있는 저장 공간들을 마련했다.
그것도 아주 귀여운 이름을 가진 쿠키저장소 라는 이름으로 말이다.
쿠키 저장소의 쿠키들은 키와 밸류 값으로 이뤄진 간단한 텍스트들이다.
브라우저마다 다르지만 쿠키는 최대 4KB 까지의 값들만을 가질 수 있으며
쿠키는 서버와 통신 할 때 , 어떤 도메인 (어떤 페이지) 에서 통신을 하느냐에 따라
요청의 header
에 담겨 보내질수 있다.
쿠키는 서버가 클라이언트와 요청을 주고 받을 때 필요한 정보들을 브라우저 단에 저장해둔 문자열이다.
벨로그에 저장된 나의 쿠키 정보를 보자
벨로그에서 테마를 다크 모드로 선택했을 때
내 브라우저 쿠키 저장소에는 theme = dark
라는 형태로 쿠키가 저장되어 있으며
해당 쿠키는 벨로그와 통신을 주고 받을 때 request header
의 Cookie
부분에서 서버 측으로 보내진다.
쿠키란 무엇일까 정리
쿠키란 클라이언트와 서버와 통신 할 때 필요한 정보들을 브라우저 메모리 단에 저장해둔 문자열이다.
브라우저에 저장된 쿠키들은 설정에 따라 서버와 통신 할 때
request header
에 담겨 전송되며
서버는Cookie
값을 이용해 클라이언트의 상태를 파악한다.서버 : 음 ~ 얘의 테마는 다크 모드군 , 음 ~ 얘의 장바구니 목록은 이렇군 ~
쿠키는 서버 단에서 생성해줄 수도 , 클라이언트 단에서 생성 할 수 있다.
우선 클라이언트 단에서 생성하는 방법을 살펴보자
브라우저 객체인 document.cookie
로 쿠키 값에 접근 할 수도 , 설정 할 수도 있다.
이처럼 쿠키를 설정한 후 개발자도구 -> application -> cookie
에 들어가면 현재 존재하는
쿠키 값 등을 볼 수 있다.
서버 단에서 클라이언트의 브라우저에 쿠키를 설정하는 방법은
response header
에서 Set-cookie
영역에서 설정할 쿠키의 이름 , 값 , 쿠키의 옵션 등을 담아 전송해주면 된다 .
간단하게 express
를 이용해서 로그인 과정을 오늘 공부해보았던 것인데
res.cookie
를 통해 쿠키의 이름 , 값 , 쿠키의 옵션을 설정하여 클라이언트에게 전송한다.
이는 Response header
의 Set-Cookie
값으로 담겨 브라우저에게 전송되며
브라우저는 Response header
의 Set-cookie
헤더를 파싱하여
자동으로 쿠키 브라우저에 저장해준다.
브라우저야 고마워 ~!
쿠키의 다양한 프로퍼티를 실습하기 위해
진행해보려 하는 토이 프로젝트의 프로토타입을 간단하게 만들어봤다.
쿠키는 다양한 용도로 많이 사용되지만 , 예시 중 하나인
사용자의 신원 정보를 통신 할 때 주고 받을 수 있도록 하는 과정인
인증과 인가를 구현해봤다.
클라이언트가 서버의 어떤 정보에 접근하기 위해선
자신이 어떤 사용자인지 확인시키는 인증이 필요하고
어떤 행위를 하기 위해서 조건이 필요하다면 , 사용자가 조건에 만족하는지 확인시키는 인가가 필요하다.
로그인 Form
을 서버에 제출하면
서버 측에선 해당 아이디와 비밀 번호를 확인 후
해당 아이디와 비밀 번호를 브라우저 쿠키 저장소에 저장한다.
app.post('/login', (req, res) => {
if (!req.body) {
res.status(400).send({ message: 'Form 이 비어있어요' });
}
const { userId, password } = req.body;
const user = db[userId];
if (!user) {
res.status(400).send({ message: '아이디를 확인해주세요' });
return;
}
if (user['password'] !== password) {
res.status(400).send({ message: '비밀번호를 확인해주세요' });
return;
}
// 아이디와 비밀번호가 데이터베이스와 맞으면 아이디오 비밀번호를 쿠키 값으로 설정
const cookieOption = {};
res.cookie('userId', userId, cookieOption);
res.cookie('password', password, cookieOption);
res.status(200).send({ message: 'good' });
});
서버 측에서 Set-Cookie
헤더를 지정해주면
브라우저 측에서 쿠키를 설정한다.
이처럼 클라이언트의 신원을 확인하는 프로세스를 인증 이라고 한다.
서버는 클라이언트의 신원을 아이디와 비밀번호를 통해 확인했고 ,
추후 인가에 사용 될 수 있는 정보(쿠키)를 브라우저 단에 저장해주었다.
이번에는 사용자가 자신의 신원정보가 들어있는 마이 페이지에 접속하려고 한다고 해보자
로그인 한 채로 마이페이지의 버튼을 눌러 접속 할 수도 있을 것이고
마이페이지의 url
에 접속 할 수도 있을 것이다.
이 때 만약 마이페이지 주소가 www.example.com/mypage/:userId
로 구성됐을 때
누군가가 :userId
에 어떤 아이디 명을 넣어도 해당 페이지에 접속이 가능하다면
신원정보가 무분별하게 유출되고 말 것이다.
이 때 클라이언트가 어떤 행위를 하려고 할 때 해당 클라이언트가 행위를 할 권한이 있는지
확인하는 것을 인가 라고 한다.
app.get('/Mypage/:userId', (req, res) => {
const { cookies, params } = req;
const { userId, password } = cookies;
const requestUserId = params.userId;
const user = db[requestUserId];
if (userId !== requestUserId) {
res
.status(404)
.send({ message: '마이페이지에서는 해당 계정에 로그인 해야 합니다' });
return;
}
if (password !== user.password) {
res.status(404).send({ message: '마이페이지 접근할 권한이 없습니다' });
return;
}
res.status(200).json(user.information);
});
위 시뮬레이션 과정에서는 마이페이지에 접근하려 할 때 리퀘스트 헤더에 쿠키를 집어 넣어 서버에 요청하고
서버는 쿠키에서 제공하는 정보를 이용해 권한이 있는지를 확인한다 .
정리
클라이언트와 서버가 리소스를 주고 받을 때 , 리소스 사용자의 정보에 따라 권한을 다르게 주고 싶을 때 사용 하는 것이 인증과 인가이다.
쿠키를 이용하여 인증과 인가를 설명한다면
클라이언트가 로그인을 통해 본인을 식별 할 수 있는 정보를 서버에 제출 , 서버에서는 확인하여 자신과 통신하고 있는 클라이언트의 정보를 식별하는 것을 인증 이라고 한다.
이런 인증된 정보는 브라우저의 쿠키로 저장되어
stateless
한HTTP
통신에서 헤더를 통해stateful
한 정보를 주고 받을 수 있다.덕분에 매번 정보를 주고 받을 때 마다 로그인 정보를 입력 할 필요가 없다.
쿠키에 담겨 헤더로 전송되는
stateful
한 정보들을 가지고 , 서버 측에서 권한을 요구하는 행위를 클라이언트가 하려 할 때 권한이 있는지 확인하는 과정을 인가라고 한다.이 때 인가 과정에서 사용되는 정보는
Cookie
헤더에 담겨있는 정보를 이용해 권한을 확인한다.
위에서 쿠키가 무엇인지, 쿠키가 왜 필요한지에 대한 예시로 인증과 인가에 대한 이야기를 나눴다.
const cookieOption = {};
res.cookie('userId', userId, cookieOption);
res.cookie('password', password, cookieOption);
기본적으로 서버측에서는 다음처럼 쿠키의 이름 , 값 , 그리고 쿠키의 프로퍼티등 을 담은 객체와 함께
Set-Cookie
헤더를 설정한다.
이 때 사용하는 프로퍼티들을 살펴보자
Cookie Scope Properties
브라우저 객체는 하나요 , 클라이언트가 사용하는 웹 페이지는 무진장 많을 것이다.
또 웹 페이지 별로 브라우저에 저장 시키는 쿠키는 더욱 많을 것이다.
이 때 특별한 설정이 없다면 , 브라우저는 서버와 통신을 주고 받을 때 브라우저에 저장된 쿠키들을 모두 헤더에 담아 전송한다.
내가 A
페이지에 접속하여 통신을 주고 받을 때 무진장 많은 쿠키를 헤더에 담아 전송한다고 생각해보자.
그러면 다음과 같은 문제들이 생길 것이다.
A
페이지와 상관이 없는 정보를 담고 있는 쿠키가 전송된다. 이를 해결하기 위해 쿠키의 프로퍼티 중 Cookie Scope
를 지정하는 프로퍼티들이 있다.
Cookie Scope
쿠키 스코프는 쿠키가 전송 될 수 있는 범위를 의미하고
이런 범위를 Cookie Scope Properties
를 통해 제어해줄 수 있다.
예를 들어 www.naver.com
에서 생성된 쿠키는 www.naver.com
서버와 통신 할 때만 전송 되도록 도메인 경로를 설정해 줄 수도 있고
www.naver.com/login
주소에만 전송 될 수 있도록 하위 경로를 지정해줄 수도 있다.
Domain
Domain
프로퍼티는 쿠키가 전송 될 수 있는 Domain
주소를 의미한다.
만약 설정되지 않는다면 Domain
값은 쿠키가 생성된 서버의 도메인 주소로 설정된다.
Domain
주소는 하위 도메인 주소 방향으로만 계층적으로 탐색 할 수 있다.
만약 Domain
이 naver.com
으로 설정된 경우 naver.com
이나 www.naver.com
으로도 전송이 가능하지만
www.naver.com
으로 설정된 경우에는 naver.com
으로는 전송이 불가능하다.
이는 보안과 관련된 의도적 설계로 , 쿠키의 범위를 제한함으로서 특정 서브 도메인의 쿠키가 다른 도메인이나 상위 도메인과 공유되지 않도록 한다.
Path
Path
는 하위 경로를 의미한다.
예를 들어 www.naver.com
까지가 도메인 주소였다면
www.naver.com/login
에서 /login
과 같은 하위 경로를 Path
라고 한다.
Path
값을 따로 설정해주지 않는다면 모든 하위 경로에 대해서 쿠키가 전송되지만
Path
값을 설정해준다면 설정된 하위 경로 이하의 모든 경로에 전송이 가능하다.
예를 들어 다음처럼 MyPage
로 이동하기 위해서는 쿠키에 설정된 아이디와 비밀번호를 전송해
인가를 받아야 할 것이다.
하지만 메뉴를 이동 할 때 아이디와 비밀번호를 전송 할 필요가 없다.
이 때 쿠키가 전송 될 Path
를 지정해줌으로서 쿠키가 조건적으로 전송이 가능하게 해줄 수 있다.
const cookieOption = {path: '/MyPage'};
res.cookie('userId', userId, cookieOption);
res.cookie('password', password, cookieOption);
res.status(200).send({ message: 'good' });
쿠키를 설정 할 때 쿠키가 전송될 경로를 /MyPage
로만 가능하게 할 경우
쿠키가 설정 될 때 Set-Cookie
헤더에 쿠키의 프로퍼티 (Path
)가 지정되고 브라우저 단에서는
쿠키의 Path
가 설정된 채로 저장된다.
/MyPage request
/content
설정된 쿠키는 Path
에서 지정된 하위 경로로 접속 할 때에만 전송이 된다.
Cookie lifecycle
쿠키의 생명주기와 관련된 프로퍼티로는 Expires , Max-Age
가 존재한다.
쿠키는 생명주기에 따라 두 가지 쿠키로 나뉜다.
Persistent Cookie
로 브라우저 디스크에 저장되며 해당 브라우저가 종료되더라도 설정된 기한 전까진 제거되지 않는다. 브라우저 메모리 , 브라우저 디스크 라고 해서 브라우저가 자체적으로 메모리와 디스크를 갖는 것이 아니라
클라이언트 컴퓨터의 메모리 , 디스크 영역에 저장되는 것이다.
Expires , Max-Age
프로퍼티는 지속 쿠키를 만들 때 사용하는 프로퍼티로 , 해당 지속 쿠키가 얼마나 디스크에 저장되어 있을 기간을 정한다.
만약 두 프로퍼티를 지정해주지 않으면 기본적으로 쿠키는 세션 쿠키 형태로 저장된다.
세션쿠키와 브라우저 쿠키 모두 장단점과 활용방안이 있다.
세션 쿠키는 메모리 단에 저장되어 있기 때문에 조회가 빠르고 임시적인 데이터들을 저장해둘 수 있다.지속 쿠키는 디스크 단에 저장되어 있기 때문에 조회가 비교적 느리지만 브라우저의 세션과 상관 없이 데이터들을 저장 할 수 있다.
브라우저의 세션은 클라이언트가 웹 브라우저를 열 때 시작되고 닫을 때 종료된다.
Expires
Expires
속성은 정확한 날짜와 시간을 지정하여 사용한다.
예를 들어 Expires=Wed, 2025년 10월 21일 07:28:00 GMT
처럼 설정하여 사용해야 한다.
const expires = new Date();
expires.setDate(expires.getDate() + 7); // 앞으로 7일 후로 설정
const cookieOption = {
expires: expires,
};
res.cookie('userId', userId, cookieOption);
Max-Age
Max-Age
는 쿠키가 생성된 시점으로부터 밀리세컨드 단위로 쿠키가 저장될 기한을 정한다.
const cookieOption = {
maxAge: 7 * 24 * 60 * 60 * 1000,
};
res.cookie('userId', userId, cookieOption);
res.cookie('password', password, cookieOption);
res.status(200).send({ message: 'good' });
});
밀리세컨드 단위이기 때문에 단위를 밀리 세컨드 단위로 변환하여 사용해준다.
sameSite
sameSite
옵션은 해당 쿠키가 전송될 수 있는 컨텍스트를 제한한다.
어떻게 제한하느냐에 따라 다른 홈페이지로의 쿠키 전송을 막을 수도 , 혹은 다른 홈페이지와 쿠키를 주고 받을 수 있다.
내가 A
사이트를 개발한 개발자이고 A
사이트는 다양한 페이지들의 글을 모아서 중계하는 ㅅ사이트라고 해보자 .
이 때 A
사이트에서 해당 글에 좋아요를 누르면, 원본 글에도 좋아요가 눌린다고 해보자.
현재 상황은 A
사이트에서 로그인하여 B
사이트의 글에 좋아요 (POST
)를 누른 상황이다.
sameSite : None
sameSite : None
의 경우에는 쿠키가 전송될 수 있는 컨텍스트를 제한하지 않기 때문에 A
사이트에서 설정된 쿠키들도 B
사이트에 좋아요 요청이 눌릴 때 같이 전송된다.
이를 통해 B
사이트에서는 A
사이트의 쿠키를 통해 클라이언트의 정보를 확인하여 좋아요 요청을 확인하는 것이 가능하다.
smaeSite : Stric
smaeSite : Stric
의 경우에는 쿠키를 동일한 홈페이지 외에는 쿠키를 제공하지 않는다. 이에 B
사이트에 좋아요 요청을 눌러도 A
사이트의 쿠키는 전송되지 않아 인가가 불가능 해진다.
하지만 A
사이트에서 제공하는 쿠키를 다른 홈페이지로 보내지 않아 보호 할 수 있다는 장점이 있다.
smaeSite : Lax
sameSite : Lax
의 경우는 좀 더 유연하게 쿠키를 전송한다. 안전한 요청으로 간주되는 최상위 도메인의 GET
요청의 경우에는 쿠키를 전송하고 , 불안전한 요청인 POST
등과 같은 경우에는 쿠키를 전송하지 않는다.
좀 더 자세한 내용은 위키피디아를 참고하자 >< 갈 길이 멀어서 쿠키에 대한 글은 여기까지만 하도록 하겠다