로그인 인증 방식, 몰라도 괜찮겠어?

김가을·2024년 3월 31일

대부분의 웹 및 앱 개발 프로젝트에서 로그인 기능은 필수적으로 구현된다. 처음에는 로그인 기능이 서버에서 단순히 처리되는 것으로 생각했다. 하지만 프로젝트를 진행하면서 다양한 로그인 방식에 대해 깊이 고민하게 되었다.

그래서 이번 기회에 정리하려고 한다.

로그인 인증, 그건 왜 하는 거지? 🧐

❓ 로그인을 하고 새로운 페이지로 이동을 할 때 계속 로그인을 요구 한다면, 이는 사용자의 입장에서 큰 불편함을 줄 것이다. 또한 로그인은 보안과 직결되기 때문에 가장 중요한 부분 중 하나이다. 그렇다면 로그인을 어떻게 안전하게 처리할 수 있을까?

먼저 로그인 API가 복잡한 이유는 HTTP 특성과 관련이 있다. HTTP는 무상태성이라는 특성을 지니고 있는데, 클라이언트에 대한 이전 상태 정보 및 현재 통신 상태가 남아있지 않다는 것을 말한다. 따라서 이를 해결하고자 로그인 방식이 별도로 존재하는 것이다.

로그인은 아래와 같은 방식으로 나뉘어진다.

  1. 쿠키 & 세션
  2. JWT
  3. OAuth

위에서 말했던 HTTP 특성 중 중요한 두 가지에 대해서 먼저 짚고 넘어가자.

  • Connectionless 프로토콜 (비연결지향)
    • 클라이언트와 서버가 한 번 연결을 맺은 후, 클라이언트 요청(Request)에 대해 서버가 응답(Response)을 마치면 맺었던 연결을 끊어버리는 것을 말한다. 즉, HTTP는 클라이언트가 서버로 Request를 한 후, 서버가 그 요청에 맞는 Response하면 그 접속을 끊는 특징이 있다.

🌟 그렇다면 HTTP 프로토콜은 왜 한 번 맺은 연결을 끊어버리는걸까?

만약 서버에서 다수의 클라이언트와 연결을 계속 유지해야 한다면, 이에 따른 많은 리소스가 발생하게 된다.

따라서 연결을 유지하기 위한 리소스를 줄이면 더 많은 연결을 할 수 있으므로 비연결적인 특징을 갖는다.

  • Stateless(무상태)
    • 통신이 끝나면 상태를 유지하지 않는 특징을 말한다. 연결을 끊는 순간 클라이언트와 서버의 통신이 끝나며 상태 유지를 하지 않는다. 비연결적인 특성으로 연결이 해제됨과 동시에 서버와 클라이언트는 클라이언트가 이전에 요청한 결과에 대해서 잊어버리게 된다.

      즉, 클라이언트가 이전 요청과 같은 데이터를 원한다고 하더라도 다시 서버에 연결을 하여 동일한 요청을 시도해야만 한다.

HTTP 프로토콜은 비연결지향무상태 라는 특징을 가지고 있기 때문에 서버와 클라이언트가 통신을 할 때 통신이 끊어지면 상태정보가 유지가 되지 않는다. 그렇다면 매번 클라이언트는 페이지를 이동할 때마다 왕복하며 인증을 해야 하는 걸까?

이러한 문제를 해결하는 방법이 쿠키와 세션이다.

쿠키(Cookie)를 사용해보자 🍪

  • HTTP의 일종으로 클라이언트가 어떠한 웹사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 작은 기록 정보 파일을 의미한다.
  • HTTP에서 클라이언트의 상태정보를 쿠키 형태로 클라이언트 PC에 저장했다가 필요 시 정보를 참조하거나 재사용할 수 있다.
  • stateless 상태에서 기억하고자 하는 데이터를 쿠키에 담고, 다음 번 요청 시에 쿠키를 요청에 함께 담아 보냄으로써 무상태를 극복한다. 즉 브라우저가 종료되어도 인증이 유지된다는 특징이 있다.

쿠키 특징

  • Key-value 쌍으로 구성되어 있는 데이터 파일이다
  • 쿠키이름, 쿠키값, 만료시간, 전송할 도메인명, 전송할 경로, 보안연결여부, HttpOnly여부로 구성되어 있다.
  • 도메인 당 20개의 쿠키를 가질 수 있다.
  • 하나의 쿠키는 4KB(= 4096 byte)까지 저장이 가능하다

쿠키 작동 방식

  1. 클라이언트가 서버에 로그인 요청
  2. 서버를 클라이언트이 로그인 요청의 유효성을 확인하고(아이디와 비밀번호 검사) 응답헤더에 set-cookie: 를 통해 쿠키를 추가하여 응답
  3. 클라이언트는 이후 서버에 요청할 때 전달받은 쿠키를 자동으로 요청헤더에 추가하여 요청한다. 헤더에 쿠키값을 자동으로 추가하여 주는데 이는 브라우저에서 처리해주는 작업이다.

⇒ 쿠키의 기한이 정해져 있지 않고 명시적으로 지우지 않는다면 반 영구적으로 쿠키가 남아있다.

쿠키의 사용목적

주로 3가지 목적을 위해 사용한다

  1. 세션 관리(Session Managemant)
    • 로그인, 사용자 닉네임, 접속 시간, 장바구니 등의 서버가 알아야할 정보들을 저장
  2. 개인화(Personalization)
    • 사용자마다 다르게 그 사람에 적절한 페이지를 보여줄 수 있다.
  3. 트래킹(Tracking)
    • 사용자의 행동과 패턴을 분석하고 기록

쿠키의 종류

  • Session Cookie
    • 일반적으로 만료시간(Expire Date)를 설정하고 메모리에만 저장되며, 브라우저 종료 시 쿠키를 삭제
  • Persistent Cookie
    • 장기간 유지되는 쿠키이다. 파일로 저장되어 브라우저 종료와 관계없이 사용할 수 있다.
  • Secure Cookie
    • HTTPS 프로토콜에서만 사용하며, 쿠키 정보가 암호화되어 전송된다.
  • Third-Party Cookie
    • 방문한 도메인과 다른 도메인의 쿠키이다. 일반적으로 광고 배너 등을 관리할 때 유입 경로를 추적하기 위해 사용한다.

쿠키값 확인 및 단점

  • 브라우저 개발자도구의 네트워크에서 쿠키값을 확인할 수 있지만, 가독성이 떨어지고 수정이 불가능하다. 하지만 ‘브라우저의 쿠키 관리 탭’ 또는 ‘쿠키 관리 플러그인’을 설치하면 쿠키를 쉽게 수정할 수 있다.
  • 이러한 쿠키는 클라이언트에서 수정할 수 있기 때문에 위변조의 위험이 항상 존재한다. 따라서 쿠키값(value)를 암호화해야 하며, 민감하거나 중요한 정보를 담지 않도록 해야한다.

세션(Session)을 사용해보자 🥄

  • 일정 시간 동안 같은 사용자로부터 들어오는 일련의 요구를 하나의 상태로 보고 그 상태를 유지시키는 기술이다.
  • 여기서 일정시간은 방문자가 웹 브라우저를 통해 웹 서버에 접속한 시점부터 웹 브라우저를 종료하여 연결을 끝내는 시점을 말한다.

⇒ 즉 브라우저가 종료되기 전까지 클라이언트의 요청을 유지하게 해주는 기술을 세션이라고 한다.

세션의 특징

  • 웹 서버에 웹 컨테이너의 상태를 유지하기 위한 정보를 저장한다.
  • 웹 서버에 저장되는 쿠키(session cookie)이다.
  • 브라우저를 닫거나, 서버에서 세션을 삭제했을 때만 삭제가 되기 때문에 쿠키보다 비교적 보안적으로 우수하다.
  • 저장 데이터에 제한이 없다.(서버 용량 허용 범위 내에서)
  • 각 클라이언트에 고유 세션 ID(Session ID)를 부여한다. 세션 ID를 통해 클라이언트를 구분하여 각 요구에 맞는 서비스를 제공한다.
  • 사용자나 다른 누군가에게 노출되면 안되는 보안적으로 중요한 정보들을 서버 안에서 다루기 위해 사용한다.

세션의 작동 방식

  1. 클라이언트가 서버에 로그인 요청
  2. 서버는 클라이언트이 로그인 요청의 유효성을 확인하고(아이디와 비밀번호 검사) unique한 id를 session ID로 생성하여 저장
  3. 서버가 응답할 때 응답헤더에 세션 ID를 쿠키에 추가하여 응답
  4. 클라이언트는 이후 서버에 요청할 때 전달받은 세션 ID를 쿠키에 자동으로 요청 헤더에 추가하여 요청
  5. 서버에서는 요청 헤더의 세션 ID 값을 저장된 세션저장소에서 찾아보고 유효한지 확인 후 요청을 처리하고 응답

세션 단점

세션의 내용은 서버에 저장되기 깨문에 계속하여 늘어날 경우 서버에 부하가 발생할 수 있다. 또한 세션에 대한 정보가 서버에 있어 쿠키에 비해 비교적 속도가 느리다.

쿠키 vs 세션

저장위치

  • 쿠키 - 클라이언트의 웹 브라우저가 지정하는 메모리 또는 하드디스크
  • 세션 - 서버의 메모리에 저장

만료 시점

  • 쿠키 - 저장할 때 expires 속성을 정의해 무효화시키면 삭제될 날짜 지정 가능
  • 세션 - 클라이언트가 로그아웃하거나, 설정 시간동안 반응이 없으면 무효화되어 정확한 시점을 알 수 없음

리소스

  • 쿠키 - 클라이언트에 저장되고, 클라이언트의 메모리를 사용하기 때문에 서버 자원을 사용하지 않음
  • 세션 - 서버에 저장되고, 서버의 메모리로 로딩되기 때문에 세션이 생길 때마다 리소스를 차지함

용량 제한

  • 쿠키 - 클라이언트도 모르게 접속되는 사이트에 의해 설정될 수 있기 때문에 쿠키로 인한 문제가 발생하는 것을 막기 위해 한 도메인 당 20개, 한 개의 쿠키 당 4KB로 제한
  • 세션 - 클라이언트가 접속하면 서버에 의해 생성되므로 개수나 용량 제한 없음

쿠키와 세션에 대해서 알아봤는데 그렇다면 세션과 쿠키를 모두 사용하는 이유가 있을까?

세션이 쿠키에 비해 보안이 높은 편이나 쿠키를 사용하는 이유는 세션은 서버에 저장되고, 서버의 자원을 사용하기 때문에 서버 자원에 한계가 있고, 속도가 느려질 수 있다.
따라서 자원관리 차원에서 쿠키와 세션을 적절한 요소 및 기능에 병행 사용하여 서버 자원의 낭비를 방지하며 웹 사이트의 속도를 높일 수 있다.

하지만 쿠키-세션 방식의 경우 세션을 서버에 저장 해야 하므로 비 효율적이고 서버 확장 시 서버 간에 세션을 공유하기 어렵다.

이러한 단점을 해결하기 위한 방법으로 토큰 기반의 사용자 인증 방법을 사용할 수 있다.

JWT(Json Web Token)는 또 뭔데?🏋

토큰이란 간단히 말해 로그인 이후 서버가 만들어서 사용자에게 넘겨주는 문자열이다. 이 문자열은 사용자 정보가 암호화 되어 있고 이 토큰을 이용하여 인증된 사용자인지 서버가 판단한다

이때 사용되는 토큰이 JWT이고 하나의 표준 인증 방식으로 이해할 수 있다.

한 마디로 JWT는 인증에 필요한 정보들을 암호화시킨 토큰이고 HTTP 헤더에 실어 서버가 클라이언트를 식별한다.

JWT 구조

JWT는 세 파트로 나누어지며 각 파트는 점으로 구분하여 xxxxx,yyyyy,zzzzz 이런식으로 표현이 된다.

순서대로 헤더(Header), 페이로드(Payload), 서명(Signature)로 구성한다.

  1. header
  • 2가지의 정보인 토큰의 종류와 해싱 알고리즘의 정보가 담겨 있다.
  • typ : 토큰의 타입을 지정한다 (바로 JWT를 말함)
  • alg: 해싱 알고리즘을 지정합니다. 보통 HMAC-SHA256 혹은 RSA 가 사용되며, 이 알고리즘은 토큰을 검증 할 때 사용되는 signature 부분에서 사용된다.
    {
    	"alg": "HS256",
    	"typ": "JWT"
    }
  1. payload
  • 토큰의 내용물이 인코딩된 부분을 말한다. 즉, 토큰에 담을 정보를 지니고 있다.
  • 여기에 담는 정보의 한 조각을 클레임(claim)이라고 부르고 이는 Json(Key/Value) 형태의 한 쌍으로 이뤄져 있다. 토큰에는 여러개의 클레임들을 넣을 수 있다.(세부 클레임 사항은 검색을 통해 보는 것이 효율적임)
  • 주로 클라이언트 고유 ID, 유효 기간 등이 포함된다
    {
    	"sub": "1234567890",
    	"name": "John Doe",
    	"iat": 1516230922
    }
  1. Signature
  • 토큰을 인코딩 하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다. 즉 인코딩된 hearder와 payload를 더한 뒤 비밀키로 해싱하여 생성한다
  • Header 및 Payload는 단순 인코딩된 값이기 때문에 해커가 복호화하고 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없다.

따라서 Signature는 토큰의 위변조 여부를 확인하는 데 사용된다.

HMACSHA256(
	base64UrlEncode(header) + "." +
	base64UrlEncode(payload),
	secret_key
)

JWT 기반 로그인 동작 과정

  1. 사용자는 id(or email)과 password를 입력하고 서버로 로그인요청을 보낸다.
  2. 서버는 db에서 회원을 조회하고 등록된 사용자인지 확인한다.
  3. 등록된 사용자라면 서버는 클라이언트 고유 ID등의 정보를 Payload에 담는다
  4. 암호화할 비밀키를 사용해 Access Tocken 토큰을 발급한다.
  5. 클라이언트는 전달받은 토큰을 저장해두고, 서버에 요청할 때마다 토큰을 요청 헤더 Authorization에 포함시켜 함께 전달한다.
  6. 서버는 토큰의 Signature을 비밀키로 복호화한 다음, 위변조 여부 및 유효 기간 등을 확인한다.
  7. 유효한 토큰이라면 요청에 응답한다.

토큰의 장점

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

토큰의 단점

  • 쿠키세션과 다르게 JWT는 토큰의 길이가 길어, 인증 요청이 많을수록 네트워크 부하가 심해진다.
  • Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다. (패스워드 등)
  • 토큰을 탈취당하면 대처하기 어렵다. 토큰은 한 번 발급되면 유효기간이 만료될 때까지 계속 사용이 가능하다.
  • 특정 사용자의 접속을 강제로 만료하기 어렵다. (쿠키/세션 기반 인증은 서버 단에서 쉽게 삭제할 수 있지만 토큰은 그게 안 됨)

이러한 단점을 보완하기 위한 토큰 전략들은 몇가지가 있다.

아래에서 살펴보자.

짧은 만료 기한 설정

토큰의 만료 기한을 짧게 설정해서 탈취되더라도 빠르게 만료시키는 방법이다. 하지만 이는 토큰이 만료되면 사용자가 다시 로그인해야 한다는 뜻이기에 사용자 입장에서 번거로운 방법이다.

Sliding Session

서비스를 지속적으로 이용하는 클라이언트에게 자동으로 토큰 만료 기한을 늘려주는 방법이다

만약 글을 작성하다가 토큰이 만료된다면 새로운 토큰을 발급해주는 것이다.

사용자가 로그인을 자주 할 필요가 없다.

Refresh Token

클라이언트가 로그인할 때 Access Token 및 Refresh Token을 발급해주는 방법이다.

Refresh Token은 Access Token보다 만료 기한이 긴 토큰이다.

클라이언트가 요청을 보냈는데 Access Token이 만료되었을 때, Refresh Token을 이용하여 Access Token의 재발급을 요청한다.

이때 서버는 DB에 저장된 Refresh Token과 비교하여 유효하면 Access Token을 발급한다.

만약 Refresh Token도 만료된 경우라면 사용자에게 로그인을 요구한다.

이 전략을 사용하면 Access Token의 만료 기한을 짧게 설정하여 위의 짧은 만료 기한 설정 전략처럼 탈취되더라도 빠르게 만료될 수 있다. 또한 짧은 만료 기한에도 불구하고 자주 로그인을 할 필요가 없어진다. 서버가 강제로 Refresh Token을 만료시킬 수도 있다.

하지만 이렇게 완벽하게 보이는 Refresh Token 발급 방법도 단점은 있다. Refresh Token 검증을 위해 DB(혹은 별도의 저장소)에 저장해야 하고, 자원이 소요될 뿐더러 추가적인 I/O 작업이 발생한다. (JWT의 장점은 I/O 작업이 필요없는 빠른 인증 처리였다.)

profile
말못하는감자에서말하는감자가되기까지

0개의 댓글