토큰과 세션(1) - 토큰과 세션 그리고 쿠키에 대해서
HTTP의 특징
connectionless, stateless
connectionless는 클라이언트에서 http request를 서버에 보내면, 서버는 알맞은 http response를 보내고 접속을 끊는 특성이 있다. 왜냐하면 여러 클라이언트와 통신을 하려면 한 커넥션이 오래 물고 있으면 성능에 좋지 않기 때문이다.
하지만 매번 연결을 끊고 다시 생성하는 비용도 만만치 않아서 (HTTP1.1에서) keep-alive를 통해서 timeout, max번 connection을 재활용하기도 한다.
stateless는 통신이 끝나면 상태를 유지하지 않는 특징이다. 로그인을 성공하고 다음에 글을 적으려고 하면 다시 인증을 해야 한다.
쿠키
HTTP는 connectionless, stateless한 성격을 가진다. 이중 HTTP의 stateless는 어디에서 누가 요청을 줬는지 알 수가 없다. 하지만 쿠키를 사용한다면 이러한 stateless한 성격을 조금이나마 없앨 수가 있다.
사용 예시로는 로그인 시 "아이디와 비밀번호를 저장하시겠습니까?" 혹은 "오늘 더 이상 이 창을 보지 않음" 등이 있다.
쿠키의 특징
- 서버에서 만든 쿠키를 브라우저에게 보내고 브라우저는 해당 쿠키를 저장하고 모든 요청에 쿠키를 넣어서 보낸다.
- 이러한 쿠키는 HTTP의 header영역을 통해서 송수신된다.
- 쿠키는 단순히 <키>=<값>의 형태의 문자열이다.
- 작은 저장 공간을 가진다. (4KB)
- 만료 시점을 기준으로 두 가지로 나뉜다.
- 브라우저가 종료되면 삭제되는 session cookies가 있다. 메모리에 저장되고 디스크에는 저장되지 않는다.
- 유효 시간을 세팅하지 않는 쿠키를 session cookie라고 한다. ex) 은행 사이트 같은 경우 다시 접속하면 끊기게
- 지정된 만료일에 삭제되는 persistent cookies가 있다.
- 300까지 저장이 가능하고, 하나의 도메인당 20개의 값만 가질 수 있다.
- 웹 브라우저마다 쿠키에 대한 자원 형태가 다르므로 브라우저간의 공유가 불가능하다.
- 보안에 취약하다.
메커니즘
- 서버에서는 Set-Cookie라는 응답 헤더에 쿠키의 정보를 넣어서 보냅니다.
- 서버로부터 쿠키를 받은 브라우저는 해당 쿠키를 클라이언트 컴퓨터의 하드디스크에 저장합니다. 그리고 브라우저가 동일한 서버에 요청을 할 때 저장해놓은 쿠키를 Cookie라는 요청 헤더에 실어서 돌려보냅니다.
- 즉, 매번 요청이 일어날 때마다 쿠키를 자동으로 헤더에 담아서 보냅니다.
쿠키는 웹 브라우저마다 저장되는 위치가 다릅니다. 하지만 모두 하드디스크에 저장이 되고 그러므로 브라우저를 껐다가 켜도 쿠키의 유효시간이 유효하다면 인증이 유지될 수 있습니다.
또한 3) 번처럼 쿠키의 특징은 브라우저라는 HTTP 클라이언트에서만 일어나는 특징입니다. 만약 `curl`과 같은 HTTP 클라이언트에서는 다른 헤더처럼 요청과 응답을 주고받아도 자동으로 값을 넣어서 요청이 보내지 진 않습니다.
쿠키 옵션
- httpOnly는 javascript를 통한 xss공격을 막아주는 설정입니다.
- path는 '/' 하위 경로로 쿠키들이 오고 갈 수 있도록 해줍니다.
- secure는 https일 때만 쿠키를 주고받을 수 있도록 해줍니다. 따라서 man-in-the-middle-attack을 막아줍니다.
- sameSite는 같은 도메인에서만 쿠키를 주고받을 수 있도록 하여 csrf 공격을 막는데 도움을 줍니다.
*csrf공격: 서비스의 기능 요청을 크로스 도메인 환경, 즉 공격자가 구성한 웹 페이지를 이용하여 사용자의 인증정보를 포함해 전송하도록 만드는 공격. 일반적으로 GET CSRF의 경우 img 태그, POST 등 다른 메소드를 이용한 CSRF는 form 태그가 많이 활용된다.
SameSite 와 CORS 차이
"origin"은 훨씬 더 엄격합니다. cors는 두 사이트 모두 동일한 스키마(HTTP/HTTPS), 포트 및 하위 도메인이 있어야 합니다.
반면에 sameSite는 TLD+1 level까지만 일치하면 sameSite라고 생각합니다.
ex) gongseek.site www.gongseek.site cors에 위반되지만 sameSite에는 위반되지 않습니다.
SameSite
- None: 아무런 제약이 없습니다. 보안에 취약하므로 cookie옵션에 secure를 설정하지 않으면 None으로 할 수 없습니다.
- Lax: 덜 엄격한 제한으로 Get의 경우에는 검증하지 않고 통과됩니다. 또한 <a 태그 또는 <form 태그가 와도 sameSite로 간주합니다.
- Strict: 엄격한 제한으로. sameSite일 때만 쿠키가 보내집니다.
세션
쿠키가 클라이언트에서 관리가 되었다면 세션은 서버에서 관리한다. 크게 Client, Server, Session Storage로 나눠서 관리가 된다. 세션 객체는 Key에 해당하는 랜덤하게 생성된 SESSION ID와 value로 구성이 되어있다. value에는 세션 생성 시간, 마지막 접근 시간, 사용자 IP등이 Map형태로 저장된다.
세션 메커니즘
- 사용자가 로그인을 하면 세션 id가 만들어지고 세션 스토리지에 저장이 된다.
- 서버에서는 브라우저의 쿠키에 SESSION ID를 저장한다.
- 브라우저는 모든 요청에 대해 SESSION ID가 담겨있는 쿠키를 전달한다.
- 서버는 쿠키에서 SESSION ID를 꺼내고, 세션 스토리지에서 꺼낸 유저의 SESSION ID와 비교하여 인증을 한다.
세션 특징
- stateful하며 서버에 정보를 저장하므로 보다 안전하다.
- 각 클라이언트에게 고유 ID 부여한다.
- 쿠키도 만료 시간이 있지만 파일로 저장되기 때문에 브라우저를 종료해도 계속 남는다. 하지만 세션은 만료시간을 정할 수 있지만 브라우저가 종료되면 만료시간에 상관없이 삭제된다.
- 매 요청마다 세션스토리지를 들어다보고 비교해야 하므로 비용이 비싸다.
- 추후 로드밸런싱을 하면 서버마다 세션ID가 달라서 난감하다 따라서 세션을 저장하는 하나의 스토리지에 모아야 하는데 이러면 모든 클라이언트의 요청마다 해당 스토리지로 오기 때문에 터질 가능성이 크다. 즉, 확장성이 어렵다.
- 동접자가 많은 서비스라면 요청의 부하가 커지므로 위험할 것 같다.
토큰
사용자 인증을 위한 정보를 서명한 것이다. 세션 기반의 인증이 클라이언트의 상태를 서버에 저장하고 요청마다 세션 스토리지에 저장된 유효한 세션인지 확인한다. 반면에 토큰 기반 인증은 토큰에 사용자 인증을 위한 정보가 담겨있으므로 서버에 사용자 정보를 저장하지 않고, 전달받은 토큰의 서명과 데이터를 검증하는 것만으로 인증이 가능하다.
토큰 메커니즘
- 사용자가 로그인을 한다.
- 서버 측에서 해당 정보를 검증한다.
- 정보가 정확하다면 서버 측에서 비밀키 또는 공개/개인 키를 이용해 서명한 토큰을 사용자에게 발급한다.
- 클라이언트는 전달받은 토큰을 저장해 두고, 서버에 요청할 때마다 해당 토큰을 전달한다.
- 서버는 토큰의 서명 값을 이용하여 검증하고 요청에 응답한다.
토큰 특징
- stateless(무상태성)과 확장성이 있다.
- 토큰이 클라이언트에 저장되므로 서버는 Stateless하며, 클라이언트와 세션간의 stateful하지 않으므로 확장하기 쉽다.
- 세션과 달리 각 서버가 해당 시크릿키로 인증을 하면 되므로 세션 스토리지같은 서버를 가지고 있지 않아도 된다.
- 저장 없이 유효한 토큰인지만 검증하므로 다른 플랫폼, 서비스 간에도 사용하기 편하다. ex) OAuth
- 토큰이 털린다면 해커가 사용자인척 요청을 할 수 있다. 따라서 만료기간을 짧게 해야 한다.
JWT 토큰
JWT는 JSON Web Token의 약자로 전자 서명된 URL-safe (URL로 이용할 수 있는 문자만 구성된)의 JSON입니다.
전자 서명은 JSON의 변조를 체크할 수 있게 되어 있습니다. base64url로 인코딩하는 이유도 jwt가 어느 곳으로 보내든, url, header, cookie에 보내든 사용이 가능하도록 하기 위함입니다.
jwt는 header, payload, signiture로 이루어져 있습니다.
header에는 토큰 타입과 해시 암호화 알고리즘이 들어있습니다. payload에는 토큰에 담을 claim(클레임)을 가지고 있습니다. claim은 name / value 의 한 쌍으로 이뤄져 있습니다. 토큰에는 여러 개의 클레임들을 넣을 수 있습니다. 클레임은 등록된(registered) 클레임, 공개(public) 클레임, 비공개(private) 클레임으로 종류가 나뉩니다. 일반적으로 등록된 클레임은 입력 값으로 무엇을 받아야 하는지가 틀이 잡혀있어서 주로 사용합니다.
토큰 전략
- slidingSession전략은 페이지를 이동할 때마다 AccessToken을 재발급해주는 방식이다.
- refreshToken을 두어 AccessToken이 만료되면 리프레시 토큰을 확인하여 재발급한다.
- refreshTokenRoation(RTR)방식으로 accessToken이 만료될 때마다 refreshToken도 재발급한다.
- rtr은 refreshToken으로부터 한 번만 accessToken을 발급하도록 하는 방식이다. 만약 refreshToken으로 한 번 더 accessToken이 발급되면 해킹당했다고 판단할 수 있다. 따라서 rtr을 사용하려면 디비저장이 불가피하다고 생각된다.
위의 방식 중 서비스에 적합하다고 생각되는 방식을 사용하면 된다. 다만 refreshToken을 디비에 저장하지 않는다면 탈취당했을 때, 발급된 refreshToken을 유효하지 않게 만들 방법이 없으므로 결국엔 디비에 저장을 해야 하는가 싶다. 그런데 또 디비에 저장하면 토큰의 장점이 사라지게 되고 그렇다면 세션을 사용하는 게 토큰보다 정보 노출이 적고, 데이터 길이도 짧아서 통신에 더 좋지 않나?라는 생각이 든다.
다음에는 확장에 어려운 세션구조를 어떻게 확장할 수 있는지에 대해서 알아보려고 한다.
출처
http://gcs.emro.co.kr:8090/pages/viewpage.action?pageId=9873130