Session vs Cookie vs JWT
HTTP의 특성
HTTP는 인터넷 상에서 데이터를 주고 받기 위한 서버/클라이언트 모델을 따르는 프로토콜이다. HTTP는 요청에 대한 응답을 처리하게 되면 연결을 끊어 버리는 비연결성(connectionless) 및 무상태성(stateless) 이라는 특징을 가지고 있다. 서버가 다수의 클라이언트와 연결을 계속 유지한다면, 이에 따른 자원 낭비가 심해지지만, 비연결성 및 무상태성의 특징을 가진다면 불필요한 자원 낭비를 줄일 수 있다는 장점이 있다.
그러나 비연결성 및 무상태성의 단점은 서버는 클라이언트를 식별할 수 없다는 단점이다.
Cookie와 Session은 비연결성 및 무상태성 특징을 보완한 기술이다.
기본적으로 HTTP 프로토콜 환경은 "connectionless, stateless"한 특성을 가지기 때문에 서버는 클라이언트가 누구인지 매번 확인해야 한다.
같은 사용자로부터 오는 일련의 요청을 하나의 상태로 보고, 그 상태가 유지되어 보이게 하는 기술이 쿠키와 세션이다.
connectionless(비연결성)
먼저 클라이언트가 요청을 보내고 응답을 하고 접속을 끊는 HTTP의 특성.
헤더에 keep-alive라는 값을 줘서 커넥션을 재활용하는데 HTTP1.1에서는 이것이 default다.
HTTP가 tcp위에서 구현되었기 때문에 (tcp는 연결지향, udp는 비연결지향) 네트워크 관점에서 keep-alive는 옵션으로 connectionless의 연결비용을 줄이는 것을 장점으로 비연결지향이라 한다.
stateless(무상태성)
연결을 끊는 순간 클라이언트와 서버의 통신이 끝나면 상태를 유지하지 않는 특성.
쿠키와 세션은 위의 두 가지 특징(비연결성, 무상태성)을 해결하기 위해 사용한다.
예를 들어, 쿠키와 세션을 사용하지 않으면 쇼핑몰에서 옷을 구매하려고 로그인을 했음에도, 페이지를 이동할 때 마다 계속 로그인을 해야 한다.
쿠키와 세션을 사용했을 경우, 한 번 로그인을 하면 그 사용자에 대한 인증을 유지할 수 있게 된다.
쿠키(Cookie)
쿠키란 클라이언트가 어떠한 웹사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 key-value 형식의 작은 기록 정보 텍스트 파일
.
쿠키를 발급하는 주체는
서버
이고 저장하는 위치는클라이언트(브라우저)
이다
MockHttpServletResponse:
Status = 200
Headers = [Set-Cookie:"userName=ysk", "password=qlalfqjsgh486"]
- 쿠키는 클라이언트의 상태 정보를 로컬(브라우저)에 저장했다가 참조한다.
- 클라이언트에 300개까지 쿠키저장 가능, 하나의 도메인당 20개의 값만 가질 수 있음, 하나의 쿠키값은 4KB까지 저장.
서버는 클라이언트의 로그인 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 Set-Cookie
에 담는다.
- Response Header에 Set-Cookie 속성을 사용하면 클라이언트에 쿠키를 만들 수 있다.
MockHttpServletRequest:
HTTP Method = GET
Request URI = /user/my/edit
Headers = [Cookie:"userName=kevin"; "password=abc123"]
쿠키 생성 - Spring
@PostMapping(value="/some/path") // Request Method는 상관이 없다.
public void ResponseEntity<?> someMethod(HttpServletResponse response) {
Cookie myCookie = new Cookie("cookieName", cookieValue);
myCookie.setMaxAge(1800); //쿠키 expiration 타임
myCookie.setPath("/"); // 모든 경로에서 접근 가능 하도록 설정
response.addCookie(myCookie);
...
}
쿠키 제거 - Spring
@PostMapping(value="/some/path") // Request Method는 상관이 없다.
public void ResponseEntity<?> someMethod(HttpServletResponse response) {
//원래 쿠키의 이름이 userInfo 이었다면, value를 null로 처리.
Cookie myCookie = new Cookie("userInfo", null);
myCookie.setMaxAge(0); // 쿠키의 expiration 타임을 0으로 하여 만료시킨다.
myCookie.setPath("/"); // 모든 경로에서 삭제 됬음을 알린다.
response.addCookie(myCookie);
}
- 쿠키는 사용자가 따로 조작하지 않아도 브라우저가 Request시에 Request Header를 넣어서 자동으로 서버에 전송
- 클라이언트 또는 서버에서 만료기한 등을 정할 수 있는데 보통 서버에서 만료기한을 정한다
- 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별할 수 있다.
예를 들어 도메인 A 내의 웹 서버에서 생성된 쿠키는 도메인 B의 웹 서버에서 사용할 수 없다.
쿠키는 쿠키가 설정된 동일한 도메인의 서버 간에만 전달될 수 있다. 마찬가지로 서버는 자체 도메인 내의 서버에만 쿠키를 설정할 수 있다.
쿠키 종류
- Session Cookie: 메모리에만 저장되며, 만료시간이 있지만 브라우저 종료시 삭제되는 쿠키
- Persistent Cookie: 파일로 저장되며, Max-Age 설정을 통해 장기간 유지 가능하고 브라우저 종료와 관계없이 사용 가능한 쿠키
- Secure Cookie: HTTPS에서 사용되는 암호화된 쿠키. 비교적 안전하지만 실질적 보안이 제공되지 않아 민감한 데이터 저장 절대 금지
- Third Party Cookie: 다른 도메인에 요청이 필요할 때 생성하는 쿠키. 주로 광고 목적으로 사용되며, 유저 개인정보 악용의 문제 발생
쿠키 동작 순서
\1. 클라이언트가 서버에 HTTP 요청
\2 .서버가 HTTP 응답시 response에 set-cookie를 통해 쿠키 생성하여 전달
\3. 클라이언트는 이제부터 매 HTTP Request시 HTTP Header에 쿠키담아서 전송
\4. 만료 기간 전이라면, 쿠키는 브라우저에 저장되어 있으며, 항상 요청시 사용 가능
\5. 만료됬다면, 클라이언트가 새로 서버에 요청하여 쿠키 새로 발급
쿠키 특징
장점
- 대부분의 브라우저가 지원
- 데이터 유효기간 지정 가능 (ex. 1 hour, 1 day)
- XSS (사이트 간 악성 Js 코드를 심는 행위)로부터 안전 - 서버에서 쿠키의 httpOnly 옵션을 설정하면, Js에서 쿠키에 접근 자체가 불가능
단점
- 용량 제한이 있어 많은 정보를 담을 수 없다 (4kb)
- 매번 서버에 HTTP 요청시 같이 전달되고 사이즈가 커질수록 서버에 부담
- 암호화가 안되어 있어 유저 정보 도난 위험
- 요청 시 쿠키의 값을 그대로 보낸다.
- 유출 및 조작 당할 위험이 존재한다.
- CSRF(사이트 간 요청 위조) 위협 - 공격자가 사용자의 요청을 가로챈 뒤 사용자의 의지와 상관없이 보안적으로 위험한 행동을 하게끔 변조하여 부당 이익을 취하는 행위
- 문자열만 저장 가능
쿠키의 사용 예
- 방문 사이트에서 로그인 시, "아이디와 비밀번호를 저장하시겠습니까?"
- 쇼핑몰의 장바구니 기능
- 자동로그인, 팝업에서 "오늘 더 이상 이 창을 보지 않음" 체크, 쇼핑몰의 장바구니
세션(Session)
사용자가 웹 브라우저를 통해 웹서버에 접속한 시점으로부터 웹 브라우저를 종료하여 연결을 끝내는 시점까지, 같은 사용자로부터 오는 일련의 요청을 하나의 상태로 보고, 그 상태를 일정하게 유지하는 기술.
즉, 방문자가 웹 서버에 접속해있는 상태를 하나의 단위로 보고 그것을 세션이라고 한다.
- 브라우저를 닫거나, 서버에서 세션을 삭제했을 때 삭제되므로, 쿠키보다 보안이 좋다.
- 쿠키와 세션은 비슷한 역할을 하며, 동작 원리도 비슷하다. 세션도 결국 쿠키를 사용하기 때문이다.
쿠키를 발급하는 주체는
서버
이고 저장하는 위치도서버
이며, 일반적으로 쿠키 내부에 JSESSIONID(Java 기준)이라는 값으로 넣어두거나 양식필드, URL로 저장된다.
서버 내부에 저장되는 고유 번호 인 세션ID로 클라이언트를 구분하여 각 클라이언트 요구에 맞는 서비스를 제공한다.
- 이 세션 ID는 애플리케이션에 In-Memory로도 저장할 수 있고 DB에 저장할 수도 있다 (Session Cluster)
세션 수립은 연결 지향 통신을 수행하는데 기초적인 요구 사항이다. 또, 세션은 무접속 방식으로 전송하는 기본 단계이기도 하다. 그러나 단일 지향성 전송은 세션을 정의하지 않는다.
- 서버는 인증된 사용자 정보를 저장하기 위해 정보를 세션 변수에 저장하여 Session을 만들고, 식별자인 session-id를 클라이언트로 응답
- 클라이언트는 HTTP 요청에 session-id를 포함시켜, 서버가 클라이언트를 식별할 수 있도록 한다.
- 세션 값은 바이너리 형식 또는 암호화된 형식으로 저장되고 서버에서만 해독될 수 있으므로 훨씬 안전하다.
- 그러나 세션은 사용자가 애플리케이션에 로그인할 때 시작되지만 로그아웃, 브라우저 종료, 서버 애플리케이션이 종료되면 자동으로 제거된다. 값을 영구적으로 저장하려면 데이터베이스에 저장해야 한다
- 각 세션은 각 사용자에게 고유하며 응용 프로그램에서 여러 세션을 사용할 수 있다.
세션의 동작 순서
\1. 클라이언트가 페이지에 요청한다. (사용자가 웹사이트에 접근)
\2. 서버는 접근한 클라이언트의 Request-Header 필드인 Cookie(일반적으로 JESSIONID)를 확인하여, 클라이언트가 해당 session-id를 보냈는지 확인한다.
\3. session-id가 존재하지 않는다면 서버는 sessionID를 생성하여 sessionID를 데이터베이스(In-memory 또는 DB)에 저장한다. 클라이언트에 대한 응답으로 쿠키와 함께 sessionId를 반환한다.
\4. 클라이언트는 재접속이나 요청 시, 이 쿠키를 이용해 sessionID 값을 서버로 다시 전송한다.
\5. 서버는 전송받은 Request의 sessionID를 저장된 sessionID이랑 비교하고, 일치한다면 요청을 수행하고 응답해준다.
세션 특징
- 웹 서버에 웹 컨테이너의 상태를 유지하기 위한 정보를 저장한다.
- 서버에 저장되는 쿠키(=세션 쿠키)
- 브라우저를 닫거나, 서버에서 세션을 삭제했을 때만 삭제가 되므로, 쿠키보다 비교적 보안이 좋다.
- 저장 데이터에 제한이 없다. (서버 용량이 허용하는 한에서)
- 각 클라이언트에 고유 Session ID를 부여한다. Session ID로 클라이언트를 구분해 각 요구에 맞는 서비스를 제공
세션 사용 예
- 화면을 이동해도 로그인이 풀리지 않고 로그아웃하기 전까지 유지
- 사용자의 로그인 상태를 모두 저장하고 있기 때문에 Stateful의 장점을 활용할 수 있는데,로그인과 관련된 다방면으로 활용이 가능하다.
- 강제 로그아웃( 그냥 세션을 삭제하면 된다. ), 접속 인원 제한( 넷플릭스 ), 로그인된 모든 디바이스 정보 확인 등등
쿠키와 세션의 공통점과 차이점
공통점
- 상태를 저장한다.
- 쿠키와 세션은 비슷한 역할을 하며, 동작 원리도 비슷하다. 그 이유는 세션도 결국 쿠키를 사용하기 때문이다.
차이점
- 큰 차이점은 사용자의 정보가 저장되는 위치이다. 쿠키는 서버에 자원을 전혀 사용하지 않으며, 세션은 서버의 자원을 사용한다.
- 보안 면에서 세션이 더 우수하다,
- 쿠키는 클라이언트 로컬에 저장되기 때문에 변질되거나 request에서 스니핑 당할 우려가 있고, 브라우저가 종료되어도 만료기간이 지나지 않는다면 계속 존재하므로 보안에 취약하다
- 세션은 만료기간을 정할 수 있지만, 브라우저가 종료되면 만료기간에 상관없이 삭제된다. 쿠키를 이용해서 session-id만 저장하고 그것으로 구분하여 서버에서 처리하기 때문에 비교적 보안성이 높다.
- 속도 면에서 쿠키가 더 우수하며 쿠키는 쿠키에 정보가 있기 때문에 서버에 요청 시 속도가 빠르고, 세션은 정보가 서버에 있기 때문에 처리가 요구되어 비교적 느린 속도를 낸다.
- 세션은 사용자가 원하는 만큼의 데이터를 저장할 수 있지만 쿠키는 4KB의 제한된 크기를 갖는다
보통 쿠키와 세션의 차이에 대해서 저장 위치와 보안에 대해서는 잘 알고 있지만, 사실 가장 중요한 것은 라이프사이클이다.
- 쿠키나 세션 만료기간이 둘 다 있지만 쿠키는 파일로 저장되기 때문애 조금 더 오래 살 수 있다.
- 세션은 브라우저를 종료하면 종료된다.
- 라이프 사이클은 쿠키도 만료기간이 있지만 파일로 저장되기 때문에 브라우저를 종료해도 정보가 유지될 수 있다. 또한 만료기간을 따로 지정해 쿠키를 삭제할 때까지 유지할 수도 있다.
Q. 세션을 사용하면 좋은데 왜 쿠키를 사용할까?
A. 세션이 쿠키에 비해 보안이 높은 편이나 쿠키를 사용하는 이유는 세션은 서버에 저장되고, 서버의 자원을 사용하기에
서버 자원에 한계가 있고, 속도가 느려질 수 있기 때문에 자원관리 차원에서 쿠키와 세션을 적절한 요소 및 기능에 병행 사용하여
서버 자원의 낭비를 방지하며 웹사이트의 속도를 높일 수 있다.
쿠키와 세션 그리고 캐시(Cache)?
캐시(Cache)는 웹 페이지 요소를 저장하기 위한 임시 저장소이고,
쿠키/세션은 정보를 저장하기 위해 사용된다.
캐시는 웹 페이지를 빠르게 렌더링 할 수 있도록 도와주고,
쿠키/세션은 사용자의 인증을 도와준다.
- 캐시는 이미지, 비디오, 오디오, css, js파일 등 데이터나 값을 미리 복사해 놓는 리소스 파일들의 임시 저장소이다.
- 저장 공간이 작고 비용이 비싼 대신 빠른 성능을 제공한다.
- 같은 웹 페이지에 접속할 때 사용자의 PC에서 로드하므로 서버를 거치지 않아도 된다.
- 그리고 이전에 사용된 데이터가 다시 사용될 가능성이 많으면 캐시 서버에 있는 데이터를 사용한다.
- 그래서 다시 사용될 확률이 있는 데이터들이 빠르게 접근할 수 있어진다. (페이지의 로딩 속도 ↑)
- 캐시 히트(hit) : 캐시를 사용할 수 있는 경우 (ex. 이전에 왔던 요청이랑 같은 게 왔을 때)
- 캐시 미스(miss) : 캐시를 사용할 수 없는 경우 (ex. 웹서버로 처음 요청했을 때)
Cookie & Session 기반 인증
쿠키를 통해 클라이언트 로그인 상태를 유지시킬 수 있었지만, 가장 큰 단점은 쿠키가 유출 및 조작 당할 위험이 존재한다는 것이다.
개인정보를 암호화 되지 않고 해독할 수 있는 HTTP로 주고 받는 것은 위험하다.
세션은 비밀번호 등 클라이언트의 인증 정보를 쿠키가 아닌 서버 측에 저장하고 관리한다.
- 사용자 정보 대신 사용자 정보를 담고 Id로 구분할 수 있는 세션 아이디를 쿠키에 담아서 전달한다.
- base64로 인코딩된 값이다.
HTTP/1.1 200
Set-Cookie: JSESSIONID=FDB5E30BF20045E8A9AAFC788383680C;
세션 - 쿠키 기반 동작 순서
1) 사용자가 로그인에 성공하면 서버가 사용자를 위한 세션을 만들고, 세션 데이터를 서비스 환경에 따라
메모리, 하드디스크, DB 등에 저장한다. ( 이때 세션을 식별하기 위한 세션 id를 기준으로 정보를 서버 측에 저장. )
2) 서버에서 클라이언트(사용자 측)로 클라이언트 식별자인 JSESSIONID를 쿠키에 담아 보내고(Set-cookie), 이 세션 id는
사용자가 인증이 필요한 활동을 하는 동안 클라이언트 측 브라우저에 있는 쿠키에 저장된다.
3) 클라이언트는 해당 사이트에 대한 사용자의 인가가 필요한 모든 요청에 세션 id를 쿠키에 담아 서버에 전송한다.
4) 서버는 클라이언트가 보낸 세션 id와 서버 측에 저장된 세션 id를 비교하여 verification(유효성 검증) 및
Authorization(인가)을 통해 해당 요청에 대한 Response를 클라이언트 측에 보낸다.
5) 이후 서버는 사용자가 처음 로그인할 때 저장된 세션 id로 쿠키에 담긴 세션 id를 확인할 수 있다.
사용자가 웹사이트에서 로그아웃하면 해당 세션 데이터가 서버 측에서 삭제된다.
장단점
- 쿠키를 포함한 요청이 외부에 노출되더라도 세션 ID 자체는 유의미한 개인정보를 담고 있지 않다.
- 그러나 해커가 이를 중간에 탈취하여 클라이언트인척 위장할 수 있다는 한계가 존재한다.
- 각 사용자마다 고유한 세션 ID가 발급되기 때문에, 요청이 들어올 때마다 회원정보를 확인할 필요가 없다.
- 서버에서 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해진다.
JWT 란? JWT 기반 인증
서버에서 인증된 사용자가 인증을 유지해주는 방법으로 보통은 세션을 사용한다. 서버 세션을 사용하면 인증된 사용자는 매우 편리하게 서비스를 이용할 수 있고, 대부분의 웹애플리케이션 서버가 세션을 지원하기 때문에 편리하다. 하지만,
- 서버를 여러대 둘 경우 (scale out),
- 같은 사용자가 서로 다른 도메인의 데이터를 요청할 경우, (SSO) 에는 세션을 유지하기 위한 비용이 매우 커진다.
이 때는 서버에 사용자 정보를 저장하는 대신 클라이언트에 사용자 정보를 내려주고, 서버는 토큰의 사용자 정보를 모든 요청에서 확인하고 서비스를 해주는 방식(sessionless)일 때, JWT 토큰이 유용하게 사용된다.
JWT(JSON Web Token)란 인증에 필요한 정보들을 암호화시킨 토큰을 의미한다.
JWT 기반 인증은 쿠키/세션 방식과 유사하게 JWT 토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별한다.
JWT 자바 라이브러리는 보통 auth0.com 에서 만든 java-jwt 라이브러리를 쓰거나, okta 에서 만든 jjwt 라이브러리를 사용한다.
- JWT 토큰 확인 : https://jwt.io/#debugger-io
- JWT : RFC7519 https://datatracker.ietf.org/doc/html/rfc7519
- JWS : RFC7515 https://datatracker.ietf.org/doc/html/rfc7515
- JWE : RFC7516 https://datatracker.ietf.org/doc/html/rfc7516
토큰을 발급하는 주체는
서버
이고 저장하는 위치는서버 혹은 클라이언트(브라우저)
이다이론적으로는 토큰을 클라이언트가 관리하게 한다.
- 하지만, 실제로 서버는 사용자 정보 캐싱이나 토큰의 유효성 평가, 혹은
refresh token
정책을 위해 서버에 토큰을 관리하기도 한다.- 이 경우, 토큰과 사용자 정보를 관리하는 방법으로 다음과 같은 방법들을 사용한다.
- redis, hazelcast
- db 저장
JWT 기반 인증
JWT 기반 인증은 Session / Cookie 인증 방식과 같이 앱 인증에서 가장 보편적으로 사용되는 방식이다.
또한 Session / Cookie 인증 방식과 유사하게 사용되는 Access Token(JWT Token)을 HTTP 헤더에 담아서 서버로 보내는 방식.
JWT 의 구조
JWT는 .을 구분자로 나누어지는 세 가지 문자열의 조합이다.
JWT는 .를 구분자로 Header
. payload
. signature
로 나뉘어진다.
Header - JWT를 검증하는데 필요한 정보를 담고 있음 (토큰 타입, 사용된 알고리즘)
Header는 alg(알고리즘)과 typ(타입)는 각각 정보를 암호화할 해싱 알고리즘 및 토큰의 타입을 지정
Payload - JWT를 통해 전달하고자 하는 데이터 — 다른 이름으로 Claim-Set
이라고 함
Payload는 토큰에 담을 정보를 지니고 있다.
key-value 형식으로 이루어진 한 쌍의 정보를 Claim이라고 칭하며 여러 Claim으로 이루어져 있다.
- 주로 클라이언트의 고유 ID 값 및 유효 기간 등이 포함되는 영역
- JWT 자체는 암호화되는 것이 아니기 때문에
민감정보를 포함해서는 안됨
- 일반적으로는 인증에 필요한 최소한의 데이터.
- 비밀번호나 전화번호등을 넣는 것은 안전하지 않다.
Claims는 Reserved Claims, Public Claims, Custom Claims 으로 구분된다 - 개발자가 자유롭게 추가하는 부분은 Custom Claims
- Reserved Claims — 미리 등록된 Claims 필수적으로 사용할 필요는 없지만 사용을 권고함(JWT 스펙에서 지정한 claim)
iss
: 토큰을 발급한 발급자 (Issuer)exp
: 만료시간이 지난 토큰은 사용불가sub
: Subject. 무엇에 관한 토큰인지nbf
: Not Before의 의미로 해당 시간 이전에는 토큰 사용불가aud
: Audience. 누구를 대상으로 한 토큰인지iat
— Issued At. 토큰이 발급된 시각jti
— JWT ID로 토큰에 대한 식별자. 토큰 자체의 아이디(일련번호?)
- Public Claims — 사용자 마음대로 쓸 수 있으나 충돌 방지를 위해 미리 정의된 이름으로 사용을 권고함
- Custom Claims — 사용자 정의 Claims (Reserved, Public 에 정의된 이름과 중복되지 않도록함)
Signatrue - 토큰의 위변조 검증을 위한 데이터
Signature는 인코딩된 Header와 Payload를 더한 뒤 비밀키로 해싱하여 생성한다.
Header와 Payload는 단순히 인코딩된 값이기 때문에 제 3자가 복호화 및 조작할 수 있지만
,
Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상복호화할 수 없다
.
따라서 Signature는 토큰의 위변조 여부를 확인하는데 사용된다.
- 서버만 알고있는 비밀 키를 이용해서 헤더에 정의된 알고리즘으로 서명값을 생성한다.
- 위변조를 시도할 떄 서버에서만 알고 있는 비밀 키를 외부에서는 알 수 없기 때문에 서명 데이터를 올바르게 생성할 수 없다.
- base64 방식으로 인코딩한 Header,payload 그리고 SECRET KEY를 더한 후 서명된다.
Header, Payload는 인코딩될 뿐 따로 암호화되지 않는다.
따라서 JWT 토큰에서 Header, Payload는 누구나 디코딩하여 확인할 수 있다.
여기서 누구나 디코딩할 수 있다는 말은 Payload에는 유저의 중요한 정보(비밀번호)가 들어가면 쉽게 노출될 수 있다는 말이다.
하지만 Verify Signature는 SECRET KEY를 알지 못하면 복호화할 수 없다.
// Signature 생성방식 pseudo-code(슈도코드)
String concatenated = encodedHeader + '.' + encodedClaims
String token = base64URLEncode(hmacSha512(concatenated, key))
JWT 동작 과정
{
Authorization: <type> <access-token>
}
- 사용자가 id와 password를 입력하여 로그인을 시도.
- 서버는 요청을 확인하고 로그인이 정상적이라면 secret key를 통해 클라이언트 고유 ID 등의 정보를 Payload에 담아 Access token을 발급한다.(JWT의 유효 기간을 설정.)
- JWT 토큰을 클라이언트에 전달 한다. (Set Header나 Set Cookie 등 - 둘 중 어느방식을 사용할지는 개발자 몫이다.)
- 클라이언트에서 API 을 요청할때 클라이언트가 Authorization header나 Cookie로 Access token을 담아서 보낸다.
- 서버는 JWT Signature를 체크하고 비밀키로 복호화한 다음, 위변조 여부 및 유효 기간 등을 확인 .
- 유효한 토큰이라면 Payload로부터 사용자 정보를 확인해 요청에 응답한다. 유효하지 않다면 예외나 액세스 거부 등을 응답한다.
JWT 장단점
장점
- 사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 세션방식과 다르게 정보를 담는 메모리를 차지 하지 않아 Stateless 서버를 구축할 수 있다.(서버 확장 및 유지보수에 유리 / 구현 방식에 따라 인증 정보에 대한 별도의 저장 공간이 필요하지 않다.)
- 수평확장이 매우 쉬움 — Session Cluster 필요 없음
- 다만, 구현 방식에 따라 AccessToken을 만료시키거나 RefreshToken 등과 같은 방법을 추가한다면 추가적인 저장소가 필요하다(Redis 등)
- 따라서, Active User가 많은 서비스에서 JWT사용이 유리함
- Session을 사용할 경우 Active User 수 만큼 Session을 저장해야 하기 때문에 스토리지 관리가 어려워짐
- Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있다.
- 외부 인증 시스템 Token을 이용한 접근이 가능하다. OAuth가 가장 대표적인 Token을 이용한 이증 방식으로 대부분의 소셜 로그인이 토큰을 기반으로 외부 인증 시스템을 구축하고 인증을 한다..
- 모바일 어플리케이션 환경에 적합하다.
단점
- 토큰 크기를 가능한 작게 유지해야 함
- 토큰 자체가 길이가 길고 항상 HTTP 요청에 포함되어야 하기 때문에 토큰이 커질수록 네트워크 부하가 심해진다.
- 유효기간이 남아 있는 정상적인 토큰에 대해 강제적으로 만료 처리가 어려움
- Session을 사용할 경우 동시 Session 제어, Session 만료 처리 등 보안상 이점이 있음 - 쿠키 / 세션방식과의 차이점
- Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.
- 토큰을 탈취당하면 대처하기 어렵다. 한 번 발급되면 유효기간이 만료될 때 까지 계속 사용이 가능하기 때문이다.
JWT 보안 문제 대체 방안
JWT 사용시 상기한 단점들을 극복하기 위해 다양한 전략을 채택할 수 있다. 각각의 전략은 장단점이 상이하기 때문에, 서비스의 특성을 고려하여 보안 수준을 높일지 사용자의 편의성을 높일지 결정해야 한다
짧은 만료 기한 설정
토큰의 만료 시간을 짧게 설정하는 방법을 고려할 수 있다. 토큰이 탈취되더라도 빠르게 만료되기 때문에 피해를 최소화할 수 있지만 사용자가 자주 로그인해야 하는 불편함이 수반된다.
- 해결방법 : Sliding Session 이나 RefreshToken
Sliding Session
글을 작성하는 도중 토큰이 만료가 된다면 Form Submit 요청을 보낼 때 작업이 정상적으로 처리되지 않고, 이전에 작성한 글이 날아가는 등의 불편함이 존재한다. Sliding Session은 서비스를 지속적으로 이용하는 클라이언트에게 자동으로 토큰 만료 기한을 늘려주는 방법. 글 작성 혹은 결제 등을 시작할 때 새로운 토큰을 발급해줄 수 있습니다. 이를 통해 사용자는 로그인을 자주 할 필요가 없어진다.
Refresh Token
클라이언트가 로그인 요청을 보내면 서버는 Access Token 및 그보다 긴 만료 기간
을 가진 Refresh Token을 발급하는 전략.
클라이언트는 Access Token이 만료되었을 때 Refresh Token을 사용하여 Access Token의 재발급을 요청한다.
서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 새로운 Access Token을 발급하고, 만료된 경우 사용자에게 로그인을 요구한다.
해당 전략을 사용하면 Access Token의 만료 기한을 짧게 설정할 수 있으며, 사용자가 자주 로그인할 필요가 없다.
- 기획이나 서비스에 따라서, 요청마다 Sliding Session처럼 AccessToken의 만료 기간을 늘리는 법도 있다.
- 또한 서버가 강제로 Refresh Token을 만료시킬 수 있다.
그러나 검증을 위해 서버는 Refresh Token을 별도의 storage에 저장해야 한다
- Redis, DB 등
이는 추가적인 I/O 작업이 발생함을 의미하기 때문에 JWT의 장점(I/O 작업이 필요 없는 빠른 인증 처리)을 완벽하게 누릴 수 없다.
클라이언트도 탈취 방지를 위해 Refresh Token을 보안이 유지되는 공간에 저장해야 한다.
RememberMe 토큰을 관리했던 것처럼
동시로그인을 허용하는 경우 : refreshToken 을 시리즈로 관리하는 방법이 있고,
유저당 로그인을 한 디바이스로 제한하는 경우 : refresh token 과 access token 을 키로 사용해서 구현할 수 있다.
Refresh Token 인증 Flow
- 사용자가 ID , PW를 통해 로그인한다.
- 서버는 요청을 확인하고 로그인이 정상적이라면 secret key를 통해 클라이언트 고유 ID 등의 정보를 Payload에 담아 Access token, Refresh Token을 발급하여 클라이언트에 응답에 담아 전달한다.
- 클라이언트는 Refresh Token은 안전한 저장소에 저장 후, Access Token을 헤더에 실어 요청을 보낸다.
- Access Token을 검증하고, 검증이 완료되면 이에 맞는 데이터를 클라이언트에 보낸다. 유효하지 않으면 예외를 보낸다.
- 시간이 지나 Access Token이 만료되거나, 만료되었다는 응답이 온다면 (여기서 만료라는 개념은 그냥 유효기간을 지났다는 의미.) 사용자는 Refresh Token과 Access Token을 함께 서버로 보낸다.
- 서버는 받은 Access Token이 조작되지 않았는지 확인한후, Token이 동일하고 유효기간도 지나지 않았다면 RefreshToken을 이용해 새로운 Access Token을 발급하고 응답에 담아 전달한다.
- 클라이언트는 새로운 Access Token을 헤더에 실어 다시 API 요청을 진행한다.
장점
기존에서 사용하던 JWT(Access Token만을 사용한 인증)보다 더욱 안전한 인증 절차이다.
- 유효기간이 더 짧기 때문이다
단점
- 구현이 복잡하며, 검증 절차가 로직이 길어진다. (frontEnd와 Server 둘다)
- RefreshToken을 저장할 저장소가 필요하다;
- 그 밖에 refresh 토큰을 허용할 경우, 보안에 취약한 점을 어떻게 보완할 것인지 고민해 보아야 한다.
간단한 SSO 사이트 구현할 때
인증 서버를 두고, 해당 인증서버로부터 토큰의 유효성을 검증하다록 하는 방식이 이상적이지만, 토큰을 다루는 방식이 동일하다면, 한 서버에서 발행한 토큰으로 SSO와 같은 효과를 볼 수도 있다.
- 인증 서버와 리소스 서버를 분리한다.
- 인증토큰을 여러 리소스 서버에 재사용한다.
참고자료
Session vs JWT
JWT
인증이 필요한 이유는? - 웹 사이트만 있는 것이 아니라 모바일 앱 등 다른 플랫폼이 있기 때문이다.- 세션과 잘 작동하는 웹사이트가 있다고 가정하자, 언젠가는 모바일(네이티브 앱) 시스템을 구현하고 현재 웹앱과 동일한 DB를 사용하려고 한다.
- 이러한 종류에는 쿠키가 없기때문에 세션 기반 인증으로 Nativa App을 사용하는 사용자를 인증할 수 없다.
- 네이티브 앱을 지원하는 또다른 백엔드를 구축하기도, 네이티브 앱 사용자를 위한 인증 모듈을 작성하기도 난감하기 때문에 토큰 기반 인증을 사용하는 것이다.
Session(세션) | JWT(Json Web Token) | |
---|---|---|
장점 | - 쿠키를 포함한 요청이 외부에 노출되더라도 세션 ID만 사용하는데, Session Id 자체는 유의미한 개인정보를 담고 있지 않다. - 각 사용자마다 고유한 세션 ID가 발급되기 때문에, 요청이 들어올 때마다 회원정보를 확인할 필요가 없다. - 쿠키와 달리 저장 개수나 용량 제한 이 없다(서버 용량 충분 시) - 서버에 저장되므로 클라이언트의 웹브라우저에 의존하지 않아도 됨 - SessionID만 보내므로, 세션의 크기가 커도 네트워크 부하가 거의 없다 JWT토큰보다 크기 작다. - Session을 사용할 경우 동시 Session 제어(접속 인원 제한), Session 만료 처리(강제 로그아웃) 등 보안상 이점, Stateful의 장점을 활용할 수 있다 서버측에서 저장/관리하기 때문에 상대적으로 온전한 상태를 유지하기 유리 그러나 공격의 위험이 있기에 유효기간, HttpOnly, Secure 옵션 등을 주어 쿠키에 저장 |
- 사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 세션방식과 다르게 정보를 담는 메모리를 차지 하지 않아 Stateless 서버를 구축할 수 있다.(서버 확장 및 유지보수에 유리 / 구현 방식에 따라 인증 정보에 대한 별도의 저장 공간이 필요하지 않다.) - 수평확장이 매우 쉬움 Session Cluster 필요 없음 - 다만, 구현 방식에 따라 AccessToken을 만료시키거나 RefreshToken 등과 같은 방법을 추가한다면 추가적인 저장소가 필요하다(Redis 등) - Active User가 많은 서비스에서 JWT사용이 유리함 Session을 사용할 경우 Active User 수 만큼 Session을 저장해야 하기 때문에 스토리지 관리가 어려워짐 - Refresh Token을 같이 사용한다면, 요청 시 마다 액세스 토큰만 검증하면 되므로 DB나 다른 스토리지를 조회하지 않아도 된다. - 세션에 비해 크기가 크다(길이가 길다.) |
단점 | - 서버에서 세션 저장소를 사용하면 Active User 수 만큼 Session을 저장하기 때문에 스토리지 문제도 있고, 요청이 많아지면 요청마다 DB를 조회해야 하므로 서버에 부하가 심해진다. - 과부하를 덜어주기 위해 서버를 여러 대를 두어 분산 환경에서 사용하게되면 세션을 쓰기가 복잡해진다. - 해커가 세션Id를 중간에 탈취하여 클라이언트인척 위장할 수 있다는 한계가 있다. - 모바일 앱 환경에서는 사용하기 어렵다. |
- 토큰 크기를 가능한 작게 유지해야 함. 토큰 자체가 길이가 길고 항상 HTTP 요청에 포함되어야 하기 때문에 토큰이 커질수록 네트워크 부하가 심해진다. - 유효기간이 남아 있는 정상적인 토큰에 대해 강제적으로 만료 처리가 어렵다 - 토큰을 탈취당하면 대처하기 어렵다. 한 번 발급되면 유효기간이 만료될 때 까지 계속 사용이 가능하기 때문이다. - Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다. |
확장성의 차이
최근 대부분의 웹 서비스가 토큰 방식을 선택하게 된 이유가 바로 확장성에 있다.
세션은 서버에 저장되기 때문에 한꺼번에 다중 접속자가 발생한다면 과부하가 걸릴 수 있다.
과부하를 덜어주기 위해 서버를 여러 대를 두어 분산 환경에서 사용하게되면 세션을 쓰기가 복잡해진다.
서버 분산 / 클러스터 환경에서 드러나는 결정적인 차이
요즘 많은 서비스들은 서버 과부하 부담을 줄이기 위해 여러 서버를 두고 서비스를 웅연한다.
그리고 앞서 언급했듯 HTTP는 stateless, connectionless 하기 때문에 request마다 내가 접속한 서버가 달라질 수도 있다!!
이렇게 되어 버리면 session 정보가 없는 다른 서버에 접속할 때마다 계속 로그인해줘야 합니다.
- 서버 분산 환경에서는 로드 밸런서가 각 request에 대한 서버를 지정해준다.
물론 이러한 세션의 단점을 해결하기 위해
sticky session
,session clustering
과 같은 방안이 나왔지만 cost, 관리포인트가 추가적으로 발생한다.
* sticky session은 처음 지정받은 서버만 사용할 수 있기 때문에 해당 서버가 터지거나 과부하가 걸려도 어찌할 도리가 없다.
session clustering은 모든 서버마다 세션을 복사해줘야 하기 때문에
상당한 메모리를 요구할 뿐 아니라 매 로그인마다 오버헤드가 크게 발생한다.
물론 세션용 서버를 따로 두고 쓰는 방법도 있지만 만일을 대비하여 서버를 복제해두어야 한다.
애초에 토큰방식을 사용한다면 추가 서버 없이 인증/인가를 처리할 수 있다.
JWT에 대한 오해와 주의할 점
JWT를 선호하는 이유 중 가장 큰 것은 '확장성' 이다. 그러나 사람들은 '어떤 규모의 확장성' 에서 문제를 겪게 될지를 인지하고 있지는 못하는 것 같다. 대부분의 사람들이 생각하는 문제가 발생할 수 있는지점은, 일반적으로 가정하고 있는 지점 보다 훨씬 더 높은 곳에 있을 것이다.
예를 들어, 우리가 운영하는 사이트는 페이스북 규모가 아니다. 페이스북 규모 정도, 몇백만개의 유효한 세션이 존재할 수 있는 상황에서야 비로소 key-value 세션의 문제가 나타날 수 있다.
그러나 통계적으로, 우리가 만드는 애플리케이션은 이용자가 적고 쉽게 돌아갈 수 있는 애플리케이션이다.
JWT를 사용하면, 속성을 추가할 수 있고 경우에 따라 서비스가 stateless 해질 수도 있으며, 이는 일부 아키텍쳐에서 바람직한 모습이 될 수도 있다. 그러나 여기에는 단점이 존재한다. 단순히 세션 저장소와 opaque token
을 추가하는 것보다 훨씬 더 복잡한 인프라를 구축해야 한다. 단순히 JWT를 쓰지말라는 것은 아니라, 선택을 함에 있어서 매우 신중해야 한다는 것이다. 보안 및 기능의 트레이드 오프에 유의해야 한다. bolierplate의 템플릿에 넣지말고, 기본 값으로 JWT를 채택해서는 안된다.
https://evertpot.com/jwt-is-a-bad-default/
세션을 사용하면 매번 요청이 올때마다 저장소를 조회하지만, 리프레시 토큰을 저장할 경우 엑세스 토큰이 만료될 때만 찌르기 때문에 같이 저장을 한다고 하더라도 이득이 있을 것같다.
즉, 리프레시 토큰을 저장하고 리프레시 토큰을 검증할 때, 엑세스토큰과 리프레시 토큰을 함께 보내서 -> 액세스 토큰이 유효한데 만료만 되었다면 리프레시 토큰과 함께 발급해주는 방향이 최선이라고 생각이 된다!
보통 JWT와 쿠키를 비교하는 사람들도 있는데, 쿠키와 JWT는 비교 대상이 아니다.
쿠키는 저장 메커니즘이고, JWT는 암호화한 인증 토큰이다.
이는 같이 쓰일 수 있기 때문에 서로 반대되는 개념이 아니다.
정확한 비교는 세션 vs JWT, 쿠키 vs 로컬 저장소다.
일반적으로 사람들이 JWT를 사용하라는 이유로는 다음이 있다.
- 확장하기 쉽다.
- 사용이 편리하다.
- 유연성 향상(웹과 모바일을 동시에 지원)
- 더 안전하다.
- Expire Date 기능
- 모바일 환경에서 더 효과적이다.
- 쿠키를 차단하는 사용자에 대해 작동된다.
확장하기 쉽다.
이는 Stateless JWT 토큰을 사용하는 경우에만 해당된다. 그러나 현실은 거의 보통 이런 종류의 확장성을 필요로 하지 않는다는 것이다. 스케일업에는 더 쉬운 방법이 많이 있으며, Reddit의 크기로 운영하지 않는 한 'Stateless 세션'이 필요하지 않을 것이다. 스캐일업의 예시로는 다음이 있다.
- 서버에서 여러 백엔드 프로세스를 실행하면 세션 저장소에 대한 Redis 데몬을 서버에서 사용하면 된다.
- 여러 서버에서 실행하는 경우: Redis를 실행하는 전용 서버를 분리한다
- 여러 서버에서 실행한 후 여러 클러스터에서의 경우: Sticky Session을 이용하면 된다.
이는 실제 유통되고 있는 서비스에서 아주 잘 지원되고 있는 방식이다. 일반적으로 2번 단계를 넘어가는 경우가 없다는 것을 고려하면, 확장하기 쉬우니 JWT를 써야 한다는 말은 김칫국부터 마시는 거랑 같다고 볼 수 있다.
사용이 편리하다.
JWT는 클라이언트와 서버 양쪽에서 세션 관리를 직접 수행해야 한다.
하지만, 표준 세션 쿠키는 서버에서만 세션을 관리해도 잘 사용할 수 있다. JWT는 쉽지 않다.
유연성 향상
이미 대부분의 주요한 세션 구현 방식들은 세션에 대해서 임의 데이터를 추가로 저장할 수 있으며 이는 JWT의 작동 방식과 다르지 않다.
더 안전하다
많은 사람들은 JWT 토큰이 암호화를 사용하기 때문에 "더 안전하다"고 생각한다. 보안적으로 signed 된 쿠키가 signed되지 않은 쿠키보다 더 안전하지만, 이것은 JWT만의 고유한 것이 아니며, 훌륭한 세션 구현도 보안적으로 signed된 쿠키를 사용한다.
"암호화를 사용한다"는 것 또한 마법처럼 뭔가 더 안전한 것을 만드는 것이 아니다; 그것은 특정한 목적을 위해, 그리고 그 특정한 목적을 위한 효과적인 해결책이 되어야 한다. 잘못 사용된 암호는, 사실, 어떤 것을 덜 안전하게 만들 수 있다.
내가 많이 듣는 '더 안전한' 주장에 대한 또 다른 설명은 '쿠키로서 보내지는 않는다'는 것이다. 이것은 전혀 말이 되지 않는다. 쿠키는 HTTP 헤더일 뿐이고 쿠키를 사용하는 것에 대해 불안할 것은 없다. 사실, 쿠키는 특히 해커가 심은 클라이언트 쪽 코드로부터 잘 보호되고 있다.
만약 누군가가 당신의 세션 쿠키를 가로채는 것에 대해 걱정한다면, 당신은 대안으로 TLS를 사용해야 한다 - 만약 당신이 JWT를 포함한 TLS를 사용하지 않는다면 어떤 종류의 세션 구현도 가로챌 수 있을 것이다.
Expire Date 기능
이것은 유용한 특징이 아니다. Expire Date 기능은 서버측에서도 똑같이 구현될 수 있다.
사실 서버측 Expire Date 기능은 더 이상 필요하지 않은 세션 데이터를 정리할 수 있게 하는 것이며, 상태 저장 JWT 토큰을 사용하고 만료 메커니즘에 의존하면 할 수 없다.
모바일 환경에서 더 효과적이다.
사용 중인 모든 모바일 브라우저는 쿠키와 세션을 지원한다. 모든 주요 모바일 개발 프레임워크와 모든 HTTP 라이브러리도 마찬가지다. 이것은 전혀 문제가 되지 않는다.
그러나 모바일 앱은 다르다.
지금까지는 JWT에 대한 편견을 깨는 말을 했다면, 이젠 JWT에서 크게 보안적으로 문제가 되는 부분에 대해서 보자
사실 JWT에 대한 심각한 문제는 다음과 같다.
많은 공간을 차지한다.
특히 모든 데이터가 토큰에 직접 인코딩되는 Stateless JWT 토큰을 사용할 경우 쿠키 또는 URL의 크기 제한을 금방 초과하게 된다. 이를 대신해서 로컬 스토리지에 저장하기로 결정할 수 있지만... -> 그건 더 위험하다.
JWT를 쿠키가 아닌 다른 곳에 저장할 때는 다음과 같은 취약점이 생길 수 있다.
We pick up where we left off: back at local storage, an awesome HTML5 addition that adds a key/value store to browsers and cookies. So should we store JWTs in local storage? It might make sense given the size that these tokens can reach. Cookies typically top out somewhere around 4k of storage. For a large-sized token, a cookie might be out of the question and local storage would be the obvious solution. However, local storage doesn’t provide any of the same security mechanisms that cookies do.
Local storage, unlike cookies, doesn’t send the contents of your data store with every single request. The only way to retrieve data out of local storage is by using JavaScript, which means any attacker supplied JavaScript that passes the Content Security Policy can access and exfiltrate it. Not only that, but JavaScript also doesn’t care or track whether or not the data is sent over HTTPS. As far as JavaScript is concerned, it’s just data and the browser will operate on it like it would any other data.
After all the trouble those engineers went through to make sure nobody is going to make off with our cookie jar, here we are trying to ignore all the fancy tricks they’ve given us. That seems a little backwards to me.
정리하자면, 쿠키를 사용하는 것은 JWT를 사용하든 사용하지 않든 선택사항이 아니라는 것이다.
JWT 토큰을 무효화할 수 없음
서버가 원할 때마다 무효화할 수 있는 세션과 달리 개별 Stateless JWT 토큰은 무효화할 수 없다. 설계상, 이는 어떤 일이 일어나든 만료될 때까지 유효하다는 것이다. 가령 공격 사용자를 탐지한 후에는 공격자의 세션을 무효화할 수 없다는 것을 의미한다. 게다가 사용자가 비밀번호를 변경할 때 이전 세션을 무효화할 수 없다
오래된 정보의 저장
이것은 토큰이 그들의 프로필에서 누군가가 바뀐 오래된 웹사이트 URL과 같은 오래된 정보를 포함하고 있다는 것을 의미한다.
참조
'CS' 카테고리의 다른 글
동기-비동기, 블로킹-논블로킹 (0) | 2023.04.23 |
---|---|
동시성과 병렬성 (Concurrency vs Parallelism) (0) | 2022.12.09 |