목차

  • [쿠키와 세션을 왜 사용할까?-HTTP의 특성](#쿠키와 세션을 왜 사용할까?-HTTP의 특성)
  • Cookie
  • [Session](#Cookie & Session 기반 인증)
  • [JWT 기반 인증](#JWT 기반 인증)

쿠키와 세션을 왜 사용할까?-HTTP의 특성

HTTP는 인터넷 상에서 데이터를 주고 받기 위한 서버/클라이언트 모델을 따르는 프로토콜입니다. 클라이언트가 서버에게 요청을 보내면 서버는 응답을 보냄으로써, 데이터를 교환합니다. HTTP는 비연결성무상태성 이라는 특징을 가지고 있습니다. HTTP는 요청에 대한 응답을 처리하게 되면 연결을 끊어 버립니다. 따라서 클라이언트에 대한 이전의 상태 정보 및 현재 통신의 상태가 남아있지 않습니다.

서버가 다수의 클라이언트와 연결을 계속 유지한다면, 이에 따른 자원 낭비가 심해집니다. 비연결성 및 무상태성의 특징을 가진다면 불필요한 자원 낭비를 줄일 수 있다는 장점이 있습니다.

그러나 서버는 클라이언트를 식별할 수 없다는 단점 또한 존재합니다. 로그인을 하더라도 다음 요청에서는 해당 클라이언트를 기억하지 못해, 또 로그인을 해야하는 문제가 발생합니다. 브라우저에서 새로고침을 누를 때마다 로그인을 해야하는 상황, 상상이 되시나요? 😱😱

하지만 우리가 사용하고 있는 웹 사이트들의 경우, 한 번 로그인 하면 다시 로그인할 필요 없이 여러 페이지를 돌아다니며 다양한 기능들을 이용할 수 있습니다. 심지어는 브라우저를 껐다 켜도 로그인이 유지가 되기도 하지요. 이는 HTTP의 비연결성 및 무상태성 특징을 보완한 기술인 Cookie와 Session 덕분입니다.

Cookie

쿠키란 클라이언트가 어떠한 웹사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 작은 기록 정보 파일을 일컫습니다.

MockHttpServletResponse:
           Status = 200
          Headers = [Set-Cookie:"userName=kevin", "password=abc123"]
  • 서버는 클라이언트의 로그인 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 Set-Cookie에 담습니다.
    • 쿠키는 Key-Value 형식의 문자열입니다.
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /user/my/edit
          Headers = [Cookie:"userName=kevin"; "password=abc123"]
  • 이후 해당 클라이언트는 요청을 보낼 때마다, 매번 저장된 쿠키를 요청 헤더의 Cookie에 담아 보냅니다.
  • 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별할 수 있습니다.

단점

  • 보안에 취약합니다.
    • 요청 시 쿠키의 값을 그대로 보냅니다.
    • 유출 및 조작 당할 위험이 존재합니다.
  • 쿠키에는 용량 제한이 있어 많은 정보를 담을 수 없습니다.
  • 웹 브라우저마다 쿠키에 대한 지원 형태가 다르기 때문에 브라우저간 공유가 불가능합니다.
  • 쿠키의 사이즈가 커질수록 네트워크에 부하가 심해집니다.

Cookie & Session 기반 인증

쿠키를 통해 클라이언트 로그인 상태를 유지시킬 수 있었지만, 가장 큰 단점은 쿠키가 유출 및 조작 당할 위험이 존재한다는 것입니다. 개인정보를 HTTP로 주고 받는 것은 위험합니다.

세션은 비밀번호 등 클라이언트의 인증 정보를 쿠키가 아닌 서버 측에 저장하고 관리합니다.

  • 세션은 쿠키를 기반하고 있지만, 사용자 정보 파일을 브라우저에 저장하는 쿠키와 달리 세션은 서버 측에서 관리합니다.
  • 서버에서는 클라이언트를 구분하기 위해 세션 ID를 부여하며 웹 브라우저가 서버에 접속해서 브라우저를 종료할 때까지 인증상태를 유지합니다.
  • 접속 시간에 제한을 두어 일정 시간 응답이 없다면 정보가 유지되지 않게 설정이 가능 합니다.
  • 사용자에 대한 정보를 서버에 두기 때문에 쿠키보다 보안에 좋지만, 사용자가 많아질수록 서버 메모리를 많이 차지하게 됩니다.
    (즉, 동접자 수가 많은 웹 사이트인 경우 서버에 과부하를 주게 되므로 성능 저하의 요인이 됩니다.)
  • 클라이언트가 Request를 보내면, 해당 서버의 엔진이 클라이언트에게 유일한 ID를 부여하는 데 이것이 세션ID다.

장단점

  • 쿠키를 포함한 요청이 외부에 노출되더라도 세션 ID 자체는 유의미한 개인정보를 담고 있지 않습니다.
    • 그러나 해커가 이를 중간에 탈취하여 클라이언트인척 위장할 수 있다는 한계가 존재합니다.
  • 각 사용자마다 고유한 세션 ID가 발급되기 때문에, 요청이 들어올 때마다 회원정보를 확인할 필요가 없습니다.
  • 서버에서 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해집니다.

쿠키와 세션의 차이

  • 쿠키는 서버의 자원을 전혀 사용하지 않으며, 세션은 서버의 자원을 사용합니다.
  • 보안 면에서 세션이 더 우수하며, 요청 속도는 쿠키가 세션보다 더 빠릅니다.
    (세션은 서버의 처리가 필요하기 때문)
  • 쿠키는 클라이언트 로컬에 저장되기 때문에 변질되거나 request에서 스니핑 당할 우려가 있어서 보안에 취약하지만,
    세션은 쿠키를 이용해서 sessionid 만 저장하고 그것으로 구분해서 서버에서 처리하기 때문에 비교적 보안성이 좋습니다.
  • 쿠키도 만료시간이 있지만 파일로 저장되기 때문에 브라우저를 종료해도 계속해서 정보가 남아 있을 수 있다.
    또한 만료기간을 넉넉하게 잡아두면 쿠키삭제를 할 때 까지 유지될 수도 있다.
    반면에 세션은 만료시간을 정할 수 있지만 브라우저가 종료되면 만료시간에 상관없이 삭제된다.
  • 세션은 정보가 서버에 있기 때문에 처리가 요구되어 비교적 느린 속도를 낸다.

JWT 기반 인증

JWT(JSON Web Token)란 인증에 필요한 정보들을 암호화시킨 토큰을 의미합니다. JWT 기반 인증은 쿠키/세션 방식과 유사하게 JWT 토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별합니다.

4.1. JWT 구조

JWT는 .을 구분자로 나누어지는 세 가지 문자열의 조합입니다. 실제 디코딩된 JWT는 다음과 같은 구조를 지닙니다.

Header

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

Payload

Payload는 토큰에

담을 정보를 지니고 있습니다. 주로 클라이언트의 고유 ID 값 및 유효 기간 등이 포함되는 영역입니다. key-value 형식으로 이루어진 한 쌍의 정보를 Claim이라고 칭합니다.

Signature

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

4.2. 인증 과정

{
    Authorization: <type> <access-token>
}
  1. 클라이언트 로그인 요청이 들어오면, 서버는 검증 후 클라이언트 고유 ID 등의 정보를 Payload에 담습니다.
  2. 암호화할 비밀키를 사용해 Access Token(JWT)을 발급합니다.
  3. 클라이언트는 전달받은 토큰을 저장해두고, 서버에 요청할 때 마다 토큰을 요청 헤더 Authorization에 포함시켜 함께 전달합니다.
  4. 서버는 토큰의 Signature를 비밀키로 복호화한 다음, 위변조 여부 및 유효 기간 등을 확인합니다.
  5. 유효한 토큰이라면 요청에 응답합니다.

4.3. 장점

  1. Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있습니다.
  2. 인증 정보에 대한 별도의 저장소가 필요없습니다.
  3. JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증됬음을 증명하는 서명 등 필요한 모든 정보를 자체적으로 지니고 있습니다.
  4. 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태가 됩니다.
  5. 확장성이 우수합니다.
  6. 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능합니다.
  7. OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있습니다.
  8. 모바일 어플리케이션 환경에서도 잘 동작합니다.

4.4. 단점

  1. 쿠키/세션과 다르게 JWT는 토큰의 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해집니다.
  2. Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없습니다.
  3. 토큰을 탈취당하면 대처하기 어렵습니다.
  4. 토큰은 한 번 발급되면 유효기간이 만료될 때 까지 계속 사용이 가능하기 때문입니다.
  5. 특정 사용자의 접속을 강제로 만료하기 어렵지만, 쿠키/세션 기반 인증은 서버 쪽에서 쉽게 세션을 삭제할 수 있습니다.

5. 보안 전략

JWT 사용시 상기한 단점들을 극복하기 위해 다양한 전략을 채택할 수 있습니다. 각각의 전략은 장단점이 상이하기 때문에, 서비스의 특성을 고려하여 보안 수준을 높일지 사용자의 편의성을 높일지 결정해야 합니다.

5.1. 짧은 만료 기한 설정

토큰의 만료 시간을 짧게 설정하는 방법을 고려할 수 있습니다. 토큰이 탈취되더라도 빠르게 만료되기 때문에 피해를 최소화할 수 있습니다. 그러나 사용자가 자주 로그인해야 하는 불편함이 수반됩니다.

5.2. Sliding Session

글을 작성하는 도중 토큰이 만료가 된다면 Form Submit 요청을 보낼 때 작업이 정상적으로 처리되지 않고, 이전에 작성한 글이 날아가는 등의 불편함이 존재합니다. Sliding Session은 서비스를 지속적으로 이용하는 클라이언트에게 자동으로 토큰 만료 기한을 늘려주는 방법입니다. 글 작성 혹은 결제 등을 시작할 때 새로운 토큰을 발급해줄 수 있습니다. 이를 통해 사용자는 로그인을 자주 할 필요가 없어집니다.

5.3. Refresh Token

클라이언트가 로그인 요청을 보내면 서버는 Access Token 및 그보다 긴 만료 기간을 가진 Refresh Token을 발급하는 전략입니다. 클라이언트는 Access Token이 만료되었을 때 Refresh Token을 사용하여 Access Token의 재발급을 요청합니다. 서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 새로운 Access Token을 발급하고, 만료된 경우 사용자에게 로그인을 요구합니다.

해당 전략을 사용하면 Access Token의 만료 기한을 짧게 설정할 수 있으며, 사용자가 자주 로그인할 필요가 없습니다. 또한 서버가 강제로 Refresh Token을 만료시킬 수 있습니다.

그러나 검증을 위해 서버는 Refresh Token을 별도의 storage에 저장해야 합니다. 이는 추가적인 I/O 작업이 발생함을 의미하기 때문에 JWT의 장점(I/O 작업이 필요 없는 빠른 인증 처리)을 완벽하게 누릴 수 없습니다. 클라이언트도 탈취 방지를 위해 Refresh Token을 보안이 유지되는 공간에 저장해야 합니다.

6. 정리

'개발 > Web' 카테고리의 다른 글

OAuth  (0) 2021.09.22

정렬 알고리즘 종류

정렬 알고리즘은 그 특지엥 따라 몇 가지로 분류할 수 있다.

비교 정렬

비교 정렬은 원소들을 정렬할 때 원소들의 순서에만 의존하는 알고리즘들을 일컫는다. 예를 들어 거품 정렬(bubble sort)은 비교하는 원소들이 숫자거나, 문자열이거나, 심지어는 복잡한 객체에 대해서도 순서가 결정되어 있다면 적용할 수 있다.

비교 정렬 알고리즘들은 아무리 빨라도 최악의 경우 Ω(nlogn)시간을 필요로 한다. 이는 이 알고리즘들을 결정 트리의 형태로 기술할 수 있다는 점에서 증명할 수 있는데, {\displaystyle n}n개의 원소들의 순열은 n!가지로 이들을 잎 노드로 가지는 결정 트리의 깊이는 스털링 근사에 따라 적어도 [logn!] ≒nlogn이어야 하기 때문이다.

비교 정렬이 아닌 알고리즘들은 이런 시간 복잡도의 제약이 없지만, 대신 다른 제약을 가질 수 있다. 예를 들어 기수 정렬은 사전순으로 표기하고 분리하기 쉬운 숫자나 문자열에 대해서는 효과적이지만, 그렇지 않은 객체들에 대해서는 일반 비교 정렬보다 느릴 수 있다.

제자리 정렬

제자리 정렬은 원소들의 개에 비해서 충분히 무시할 만한 저장 공간만을 더 사용하는 정렬 알고리즘들을 의미하며, 제자리 알고리즘의 범위에 포함된다. 예를 들어 삽입 정렬은 이미 주어진 원소들을 옮긴 뒤 적절한 위치에 원소를 삽입하는 연산을 반복하는데, 이 과정에서 원소들을 담는 공간 외에 추가로 사용될 수 있는 공간은 옮겨지는 원소가 저장되는 공간과 루프 변수 정도에 불과하다.

퀵 정렬은 제자리 알고리즘의 정의에 따라서 제자리 정렬로 분류할 수도 있고 아닐 수도 있다. 퀵 정렬은 재귀적으로 정의되기 때문에 스택을 사용하게 되는데, 이 스택의 깊이는 {\displaystyle n}n개의 원소에 대해 {\displaystyle \Omega \left(\log n\right)}\Omega \left(\log n\right)에 비례하므로 전체 공간 복잡도는 상수가 아니다. 하지만 실용적인 의미에서 퀵 정렬은 상대적으로 작은 메모리만을 더 사용하기 때문에 흔히 제자리 정렬로 기술된다.

온라인 정렬

온라인 정렬은 모든 원소들이 처음부터 주어지지 않고 차례대로 들어 오는 경우도 처리할 수 있는 정렬 알고리즘을 의미하며, 온라인 알고리즘에 해당된다. 대표적으로 합병 정렬은 이미 정렬된 여러 개의 부분 리스트를 관리하다가 매 원소가 들어 올 때마다 적절한 리스트에 추가하고 필요하면 리스트들을 합병하는 식으로 온라인 정렬로 만들 수 있다.

저명한 정렬 알고리즘

정렬 알고리즘의 수는 많지만 실용적인 구현체의 경우 일부의 알고리즘들이 우위를 차지하고 있다. 작은 데이터 집합의 경우 삽입 정렬이 널리 사용되는 반면 큰 데이터 집합의 경우 점근적으로 효율적인 정렬이 사용되며 여기에는 주로 힙 정렬, 합병 정렬, 퀵 정렬이 있다. 효율적인 구현체들은 일반적으로 '정렬 전반을 위한 점진적으로 효율적인 알고리즘'을 '재귀 하한의 작은 리스트들을 위한 삽입 정렬'과 병합한 하이브리드 알고리즘을 사용한다. 상당한 튜닝을 거친 구현체들은 안드로이드, 자바, 파이썬에 쓰이는 타임소트(합병 정렬, 삽입 정렬, 추가 로직), 일부 C++ sort 구현체와 닷넷에 (여러 형태로) 쓰이는 인트로소트(퀵정렬과 힙 정렬) 등 더 복잡한 종류를 사용한다.

고정 간격의 숫자와 같은 더 제한된 데이터의 경우 계수 정렬이나 기수 정렬과 같은 분산 정렬이 널리 쓰인다. 거품 정렬과 같은 종류들은 실제로는 드물게 쓰이지만 교육 및 이론 토론에서 흔히 볼 수 있다.

물리적으로 객체를 정렬할 때(예: 논문, 테스트, 책을 알파벳 순으로 정리) 사람들은 작은 집합에 대해 직관적으로 삽입 정렬을 사용하는 것이 보통이다. 큰 집합의 경우 사람들은 보통 이니셜 문자와 같은 최초 버킷을 사용하며 매우 큰 집합의 경우 여러 버킷을 사용하는 것이 정렬에 실용적이다. 이를테면 객체들을 바닥 위나 넓은 공간 위에 퍼트려놓는다든 방식으로 공간이 상대적으로 저렴한 경우가 있는데, 이때 특히 객체를 먼 공간으로 움직이는 등의 조작에 대한 비용은 높아지게 되므로 참조의 지역성은 중요하다. 합병 정렬은 특히 두 손을 사용하는 등 물리적인 물체를 다루기 실용적인 반면 힙 정렬이나 퀵 정렬 등 다른 알고리즘들은 인간이 직접 사용하기에는 적절치 않다. 공간을 남기는 삽입 정렬의 변종인 라이브러리 소트 등의 다른 알고리즘들 또한 물리적 이용에 실용적이다.

단순 정렬

가장 단순한 정렬 중 삽입 정렬과 선택 정렬이 있으며 이 둘 모두 낮은 부하로 인해 작은 데이터에 효율적이지만 큰 데이터에는 효율적이지 않다. 삽입 정렬은 대부분 정렬된 데이터에 대해 비교를 덜 하고 성능이 좋은 이유로 일반적으로 선택 정렬보다 현실적으로 더 빠르기 때문에 선호되는 경향이 있으나 선택 정렬은 쓰기를 덜 하므로 쓰기 성능에 제약이 있는 환경에 사용된다.

효율적인 정렬

실용적인 일반 정렬 알고리즘들은 평균 시간 복잡도(및 일반적으로 최악의 경우의 복잡도) O(n log n)의 알고리즘에 기반한 경우가 대부분이며 그 중 가장 흔한 것이 힙 정렬, 합병 정렬, 퀵 정렬이다. 제각기 장단점이 있으며, 단순 합병 정렬의 구현체는 O(n) 추가 공간을 사용하며 단순한 퀵 정렬 구현체는 O(n2) 최악의 경우 복잡도를 사용하는 것이 가장 큰 특징이다. 이 문제들은 더 복잡한 알고리즘의 비용으로 해결되거나 개선할 수 있다.

이 알고리즘들이 다양한 수정이 수반되는 실생활 데이터를 기준으로 랜덤 데이터에 점근적으로 효율적이다. 첫째로, 이 알고리즘들이 일으키는 부하는 크기가 작은 데이터일수록 중대한 까닭에 하이브리드 알고리즘이 사용되기도 하며 데이터가 충분히 작으면 보통 삽입 정렬로 전환된다. 둘째로, 알고리즘은 이미 정렬된 데이터나 거의 정렬이 마무리된 데이터에 대해 성능이 떨어지는 경우가 있기 때문에 이 알고리즘들은 실생활 데이터에 일반화되어 있으며 대략적인 알고리즘들에 의해 O(n) 시간으로 정렬이 가능하다. 끝으로, 이 알고리즘들은 불안정 정렬에 속할 수 있는데 정렬 시 안정성은 바람직한 속성으로 간주되기도 한다. 그러므로 타임소트(합병 정렬 기반) 또는 인트로소트(퀵 정렬 기반으로, 필요 시 중간에 힙 정렬로 변경됨)와 같은 더 복잡한 알고리즘들이 적용되기도 한다.

'자료구조 & 알고리즘' 카테고리의 다른 글

탐색 알고리즘  (0) 2021.09.16
시간 복잡도 차트  (0) 2021.09.16
백트래킹  (0) 2021.09.16
스택  (0) 2021.08.20

+ Recent posts