9주차 - 1) 소셜 로그인

변현섭·2023년 6월 18일
0

4th UMC Server-Spring Study

목록 보기
26/30

Ⅰ. 핵심 키워드

1. Stateless

Stateless(무상태성)란, 서버가 클라이언트의 상태를 보존하지 않는다는 것을 의미한다. 즉, 서버는 클라이언트를 기억하지 않으며, 오직 클라이언트의 요청에 대해 응답만 하게 된다. 이를 다르게 말하면, 클라이언트 측에서 보낸 첫 요청과 다음 요청 사이의 연관관계가 존재하지 않는다는 것이다.

HTTP 통신은 무상태성을 지향한다. 그 이유는 서버를 무한 확장할 수 있기 때문이다. Stateless의 경우 클라이언트가 정보를 보관하므로, 서버의 장애로 인한 문제의 영향을 적게 받는다. 다만, 클라이언트가 서버에 요청을 보낼 때 전송해야 할 정보가 늘어난다는 것은 단점이다.

오늘 우리가 배울 개념 중에 매우 중요한 내용이 바로 JWT인데, 이 JWT를 예시로 설명해보겠다. JWT를 활용해 로그인을 할 때 서버는 Stateless 상태가 된다. 대신 JWT는 Payload라고 하는 부분에 클라이언트에 대한 정보를 담는데, 이 부분이 매우 길어진다는 것이다. 따라서 무조건 무상태성이 좋다고만 이야기 할 수는 없겠으나, 상황에 따라 유용하게 활용할 수 있음은 분명하다.

2. Requset Header를 이용한 로그인 방식

우리가 흔히 로그인이라 부르는 사용자 인증 방법에는 여러가지 방식이 존재한다. 쿠키 인증 방식, 세션 인증 방식, JWT 인증 방식, OAuth 인증 방식이 그것이다. 이들을 모두 Request Header를 이용한 로그인 방식일 수 있다.

Request Header를 이용한 사용자 인증이란, 웹 애플리케이션이 클라이언트의 요청을 보낼 때 HTTP 요청 헤더에 포함된 인증 정보를 사용하여 클라이언트의 신원을 확인하는 방법이다. 이는 웹 애플리케이션에서 사용자 인증을 수행하는 방법 중 가장 일반적인 방법으로 사용되고 있다.

※ 사용자 인증이 필요한 이유
위에서 HTTP 통신은 Stateless를 지향한다고 배웠다. 즉, 서버는 현재 누가 로그인 중에 있는지를 기억하고 있지 않다는 것이다. 하지만 누군가는 이를 기억하고 있어야 한다. 서버의 무상태성은 유지하고 싶은데, 로그인 상태는 기억해야하기 때문에 사용자 인증이 필요한 것이다.
다시 말해 서버의 Stateless를 구현하기 위해선 클라이언트가 Stateful해야 한다는 것이다. 쿠키, 세션, JWT, OAuth 중 어떤 방식을 사용하느냐에 따라 요청 헤더에 포함되는 내용은 다르지만, 이들은 모두 클라이언트가 로그인 상태임을 서버에게 알려주는 역할을 수행한다. 이로써, 클라이언트의 로그인 사실을 클라이언트가 기억하도록 만드는 것이 가능해진다.

HTTP 요청 헤더에는 다양한 정보가 포함될 수 있으며, 그 중에서도 가장 일반적인 사용자 인증 방식은 요청 헤더의 "Authorization" 필드를 사용하는 것이다. 이 헤더는 클라이언트가 서버에게 자신의 신원을 증명하기 위해 사용자 이름과 비밀번호, 토큰 등을 인코딩하여 포함할 수 있다. 저번 포스팅에서, 서버에게 받은 JWT의 값을 요청 헤더에 넣어 닉네임을 변경하는 실습을 진행해보았기 때문에 이 부분을 이해하는 데에 큰 무리는 없을 것 같다.

Request Header를 이용한 로그인 방식에는 한가지 주의할 점이 있다. 암호화되지 않은 HTTP 통신을 사용할 경우 보안 상의 문제가 발생할 수 있다는 것이다. 따라서 HTTPS와 같은 보안 프로토콜과 함께 사용하는 것이 필수적이다.

1) 개념

쿠키는 웹 브라우저에서 웹사이트에 접속할 때 생성되는 작은 데이터 조각이다. 이 데이터는 클라이언트(웹 브라우저)와 서버 간의 상태 정보를 저장하고 교환하는 데 사용된다. 쿠키의 특징은 아래와 같다.

  • 쿠키는 Key-Value 쌍의 문자열로 이루어진다.
  • 쿠키는 최대 4KB까지 저장할 수 있고 최대 300개까지 만들 수 있다.
  • 브라우저마다 쿠키의 지원형태가 달라 브라우저 간의 공유는 불가하다(크롬에서 로그인했다고 해서 마이크로소프트 엣지에서도 로그인되지 않는다는 의미로, 브라우저가 다르면 서버는 다른 사용자로 인식한다는 뜻이다).
  • 요청 시, 쿠키의 값을 그대로 보내기 때문에 보안이 취약하다
  • 요청마다 보내야 하는 쿠키의 사이즈가 커지면, 네트워크 부하를 초래할 수 있다.

재미삼아 우리가 먹는 쿠키를 생각해보자. 쿠키의 특징하면 무엇이 떠오르는가? "가지고 다니기 쉽다", "던지기 쉽다(?)", "잘 부서진다" 정도가 있을 거 같다. 이러한 특징이 쿠키에서도 나타난다.

쿠키는 클라이언트의 정보를 저장할 수 있는 유일한 공간이다. 따라서 클라이언트가 저장해 두어야 하는 모든 정보는 이 쿠키에 들어간다. 그러므로 클라이언트는 쿠키를 항상 가지고 다녀야 하고 필요에 따라 요청과 함께 던질 수 있어야 한다. 그럼 잘 부서진다는 건 뭘까? 쿠키에 "모든 정보"가 다 들어있다는 건, 개인 식별 정보가 포함될 수도 있다는 것이다. 쿠키는 보안이 약하기 때문에 개인정보 보호 및 보안에 각별한 주의가 요구된다. 쿠키에 담은 정보는 잘 부서질 수 있는 정보임을 명심하자. 아래와 같은 기능들이 쿠키를 통해 얻는 편의기능들이다.

  • 쇼핑몰의 장바구니 기능
  • 클라이언트의 정보를 파악해 적절한 광고를 노출
  • 로그인 기록이 있는 사이트의 아이디/비밀번호를 자동으로 입력 또는 자동 로그인
  • 팝업 창의 "오늘 이 창을 다시 보지 않기"

※ 쿠키와 캐시의 차이
쿠키와 캐시는 클라이언트에서 관리하는 데이터라는 공통점이 있다. 브라우저를 사용하다보면 "쿠키 삭제" 또는 "캐시 삭제"를 본 적이 있을 것이다. 이 둘은 어떤 차이가 있는걸까?

정리하면, 캐시는 웹 렌더링을 빠르게 하기 위해 저장하는 데이터로, 오디오, 비디오, 이미지 파일 등이 빠르게 로딩될 수 있도록 돕고, 쿠키는 유저 개인의 정보를 저장하여 편의기능을 제공해주는 것이다. 사용자인증에 캐시는 사용되지 않는다.

※ 캐시와 캐시 메모리의 차이
캐시는 잠시 저장해둔다는 의미로써 기능을 의미하고, 캐시 메모리는 캐시 기능을 이용한 메모리이다. 캐시 메모리는 자주 사용되는 데이터를 주기억 장치가 아닌 캐시 메모리에 저장하여 속도를 향상시킨다.
마찬가지로, 브라우저 캐시도 다시 사용될 수 있는 데이터를 빠르게 다시 보여주는 역할을 수행한다. 브라우저의 뒤로가기 버튼이나 앞으로가기 버튼이 캐시를 활용한 대표적인 예이다. 캐시는 종류에 따라 저장되는 방법이 다른데, 캐시가 캐시메모리에 저장되는 것은 아니다.

2) 쿠키를 활용한 로그인 방식

혹시나 헷갈리는 사람이 있을까봐 이야기하면, 여기서 말하는 클라이언트는 브라우저라고 생각하면 된다. 즉, 클라이언트가 정보를 저장해야한다는 말이나, 쿠키를 계속 전송해주어야 한다는 말 모두 브라우저의 역할에 대해 이야기하는 것이다. 여기서 클라이언트를 사용자로 오해하지 않도록 한다.

① 클라이언트의 첫 요청에는 쿠키가 포함되지 않는다. 일반적으로 요청 객체의 body에 정보를 담아 전송한다.

② 서버는 요청에 대한 응답을 보내면서, 클라이언트에게 저장시키고 싶은 정보를 응답 헤더의 Set-Cookie에 담는다. 여기에 쿠키의 만료일자가 포함될 수 있다(포함하지 않으면 만료기간이 없는 쿠키이다).

③ 클라이언트는 쿠키를 받아 저장하고, 이후부터는 해당 도메인의 웹사이트에 요청을 보낼 때마다 쿠키를 함께 전송한다.

④ 서버는 쿠키에 담긴 정보를 통해 클라인언트가 누군지 식별하고 인증을 유지시킨다.

⑤ 로그아웃 요청을 받거나 서버가 정해준 쿠키의 만료 시간이 지나면, 쿠키는 더 이상 사용할 수 없게 된다.

※ 만료된 쿠키
만료된 쿠키를 삭제하는 것은 서버가 할 일이 아니라 오롯이 클라이언트의 책임이다. 하지만, 브라우저는 만료된 쿠키를 자동으로 삭제해주지는 않는다. 즉, 만료된 쿠키가 쿠키저장소에 그대로 남아있다는 말이다. (물론, 만료된 쿠키는 저장소에만 있을 뿐 서버로 전송되지는 않는다.) 만료된 쿠키를 삭제하려면 브라우저의 사용자가 명시적으로 삭제를 요청해야 한다.
실제로 컴퓨터의 속도 향상과 보안 강화를 위해 주기적으로 쿠키를 삭제해주는 것이 좋다고 한다. 다만, 만료기간이 없는(또는 아직 만료되지 않은) 쿠키까지 다 지워버리면 사용자 불편이 초래될 수 있다.

4. Session

1) 개념

세션이란, 사용자가 브라우저를 통해 웹 서버에 접속한 시점부터 종료하는 시점까지 사용자에 대한 정보를 서버에 기록하고 보관함으로써 사용자를 관리하는 서버의 저장공간이다. 여기서 한가지 의문이 생겨야 한다. 서버가 저장을 한다는 건 Stateless를 위배하고 있는 것이다. 사실 Session 방식은 쿠키의 취약한 보안을 해결하기 위해 Stateless를 포기한 경우이다. 세션의 특징은 아래와 같다.

  • Key-Value 쌍으로 이루어진다.
  • 보안에 취약한 쿠키에 민감정보를 저장하는 대신 서버 측에 저장하고 관리한다.
  • 사용자 식별자로 session ID가 사용되며 쿠키를 통해 전달된다.
  • 사용자가 많아질 경우 정보를 찾는 데(데이터 매칭) 오랜 시간이 걸릴 수 있다.
  • session ID에는 개인정보가 없기 때문에 이를 탈취한다고 해서 사용자의 정보를 알 수는 없다.
  • 쿠키인증보다 안전한 방법이긴 하나, 여전히 탈취한 session ID를 이용해 위장 요청이 가능하다는 문제가 있다. 이를 하이재킹 공격이라 한다.

2) 세션을 활용한 로그인 방식

세션을 활용한 로그인 방식이라고 해서 쿠키를 활용한 로그인 방식과 완전히 별개인 것은 아니다. 세션 로그인에도 쿠키가 사용된다. 다만 쿠키를 활용한 로그인 방식에서와 조금은 다른 쿠키가 사용될 뿐이다.

쿠키는 크게 두가지로 분류된다. 바로 영구쿠키(지속쿠키)와 세션쿠키이다. 먼저, 영구 쿠키는 쿠키인증방식에 사용되는 쿠키로 클라이언트의 하드드라이브에 저장된다. 영구라는 말 그대로 쿠키가 만료된 이후 또는 (만료가 안 된 경우라면) 브라우저가 종료된 이후에도 계속 유지된다. 특히 만료되지 않은 쿠키는 나중에 다시 해당 도메인에 접속할 때 활용된다.

반면, 세션쿠키는 세션을 유지하기 위한 식별자로 RAM에 저장된다. 이 쿠키에는 만료날짜 및 시간이 존재하지 않는다. 대신 웹 브라우저가 종료될 때 자동으로 삭제된다. 즉, 세션쿠키는 클라이언트가 웹 애플리케이션과 상호작용하는 동안에만 유효하다.

① 사용자가 로그인을 요청하는 첫 요청에는 마찬가지로 쿠키가 포함되지 않고 요청 객체의 body에 정보를 담아 전송한다.

② 서버는 회원을 확인한 후, 세션 객체를 생성해 session ID를 저장하고 이 세션 객체를 세션 저장소에 저장한다. 세션 저장소는 서버 메모리(서버의 RAM)일 수도 있고, 데이터베이스나 캐시 같은 외부저장소일 수도 있다.

③ 로그인에 대한 응답으로 session ID를 Set-Cookie에 담아 보낸다.

④ 클라이언트는 쿠키를 저장해두고 있다가 요청을 할 때마다, 요청 헤더에 session ID를 담아 전송한다.

⑤ 서버는 클라이언트에게 받은 session ID를 확인해 이와 일치하는 세션 객체를 찾는다.

⑥ 일치하는 세션 객체를 찾으면 요청에 대한 응답을 보낸다.

5. JWT

1) 등장 배경

기존의 인증체계는 Cookie와 Session의 전성시대였다. 쿠키는 클라이언트가 웹사이트에 접속할 때, 그 사이트에서 사용하는 일련의 작은 기록파일이다. 서버가 클라이언트에게 정보를 전달할 때, 저장하고자 하는 정보를 응답 헤더에 담아 전달한다.

쿠키를 사용하는 이유는 특정 정보를 저장해야하기 때문이다. 로그인을 예시로 들어보자. 로그인을 하면 Set-Cookie의 형태로 쿠키를 반환받게 되고, 로그인이 필요한 요청을 할 때마다 받은 쿠키를 이용해 동작한다. 만약 쿠키가 없었다면, 특정 요청을 할 때마다 매번 사용자가 로그인을 다시 해야하는 번거로움이 생겼을 것이다. 하지만 쿠키로 인해 로그인 한 상황에선 그 상태를 유지한 채 요청을 할 수 있게 되는 것이다.

하지만 쿠키는 보안에 취약하다는 특징이 있다. 이를 보완하기 위한 방법이 바로 세션이다. 즉, 쿠키를 이용하여 매번 로그인하는 번거로움을 해결하자는 취지는 같지만, 직접적인 아이디와 패스워드를 주고 받지는 않겠다는 의미이다. 쉽게 설명하면 마치 서버에서는 자물쇠를 가지고 있고, 클라이언트에겐 그 자물쇠에 맞는 키를 주는 것이다.

구체적으로 이야기하면, ID, PW를 인증한 사용자를 식별할 수 있는 unique한 세션 ID를 만들어 서버의 세션 저장소에 저장한다. 그리고 이 세션 ID를 쿠키나 JSON 형식으로 만들어 클라이언트에게 반환한다. 클라이언트는 사용자 인증이 필요할 때마다 받은 세션 ID를 쿠키에 담아 서버에 전달한다. 서버는 그 세션 ID가 세션 저장소에 있는지 확인한 후 요청을 처리한다.

하지만 이것도 완벽한 방법은 아닌게 여전히 세션 ID가 탈취당할 위험성이 존재한다. 물론 이 경우에는 세션 저장소를 지워버리면 해결될 일이지만, 이 때문에 다른 정상 사용자들까지 인증을 못하게 되는 상황이 발생하게 된다. 더 중요한 것은 stateless를 위배한다. 서버에 세션ID(상태)를 저장해야한다는 점은 서버 확장(scale out)을 어렵게 만든다. 아래의 그림처럼 scale out 서버에는 session 저장소가 없으므로 auth server에 세션 ID를 또 요청해야 할 것이다.

사용자가 많아질수록 메모리에 부담이 될 수 있고, 요청시마다 매번 세션 저장소를 조회하는 것도 큰 단점이다. 이러한 단점을 해결할 방법으로 등장한 것이 JWT이다. JWT는 JSON Web Token의 약자로, JSON 객체를 사용하여 클라이언트와 서버 사이에 정보를 안전성 있게 전달한다. 일반적으로 회원가입, 로그인 등 사용자 인증 관련 작업에 사용된다.

2) JWT

JWT는 서명된 토큰으로 인터넷 표준 인증 방식의 일종이다. 말 그대로 인증에 필요한 정보를 토큰에 담아 암호화시켜 사용하는 방식이다. public key-private key의 쌍을 이용해 토큰에 서명할 경우, 개인 키를 보유한 서버만이 토큰의 유효성을 검증할 수 있다. JWT는 Header, Payload, Signature로 구성되어 있고 아래 처럼 각 구성요소를 .으로 구분한다.

① Header

  • 토큰의 타입이나 서명, 생성에 어떤 알고리즘이 사용되었는지를 저장한다.

② Payload

  • 보통 Claim이라고 하는 사용자 또는 토큰에 대한 property를 key-value쌍으로 저장한다. 여기서 Claim이란, 토큰에 사용할 정보의 조각을 의미한다. JWT의 표준 스펙은 아래와 같다.
  • 표준 스펙 중에서 원하는 값만 포함해서 사용할 수도 있고 모두 포함해서 사용할 수도 있다. 물론 커스터마이징 claim도 가능하다.
  • 주의해야 할것은 header와 payload에는 민감성 정보를 담지 않아야 한다는 것이다. Json을 base64로 인코딩하긴 하지만, 이는 암호화가 아니기 때문에 서버가 아닌 누구라도 마음만 먹으면 값을 확인할 수 있다.

    ※ Base 64인코딩
    아스키문자(128문자)에서 제어문자를 뺀 64개의 기본적인 문자로 인코딩하는 방식으로, 이진데이터 전송에 널리 쓰이는 방법이다.

③ Signature

  • Signature는 Header와 Payload를 대상으로 Base64URL-safe Encode를 적용하고, 이를 비밀키로 서명한 것이다.

    ※ Base64URL-safe Encode
    Base64인코딩에는 '+'나 '/'와 같은 문자가 포함되어 있는데 URL에서 '+'는 공백을, '/'는 경로 구분을 의미하므로 혼동이 야기될 수 있다. 따라서 '+'대신 '-'를, '/' 대신 '_'을 사용하여 인코딩함으로써 URL에서 안전하게 사용되도록 인코딩하는 방식을 Base64URL-safe Encode라 한다.

  • Signature에서 사용하는 알고리즘은 헤더에서 정의한 방식을 따른다.

  • Signature에서는 header + payload에 서버의 개인 key값을 합한 결과를 헤더에서 정의한 알고리즘에 따라 암호화한다. Header나 Payload와는 달리 Signature 값은 비밀키 없이는 그 누구도 복호화할 수 없다. Signature는 토큰의 위변조 여부를 확인하는 역할을 수행한다.

3) JWT를 활용한 로그인 방식

JWT를 활용한 로그인 방식은 쿠키를 사용할 수도 있고 사용하지 않을 수도 있다. 사실, 쿠키를 사용하지 않는 경우를 일반적인 경우로 본다. 쿠키를 사용한 로그인 방식은 요청을 보낼 때 자동으로 쿠키가 함께 전송되지만, 쿠키를 사용하지 않을 경우 JWT를 명시적으로 포함시켜 전달해야 한다. JWT는 보통 HTTP의 요청 헤더나, 요청본문, URL의 쿼리스트링에 포함된다.

이는 곧 JWT를 누구나 다 알 수 있게 된다는 것이다. 우리가 JWT를 사용하는 이유는 토큰의 위변조를 막기 위함이지, 정보 보호를 위한 것은 아니다. 따라서 JWT에는 민감정보를 포함하지 않도록 한다.

① 사용자가 로그인 요청을 보낸다.

② 서버는 회원을 확인한 후 서명된 JWT를 생성해 클라이언트에 응답한다. 이 때, Access Token과 Refresh Token을 같이 전달한다.

③ 사용자는 요청을 보낼 때마다 Access Token을 함께 전송한다.

④ 서버에서 Access Token을 검증하고 응답을 보낸다.

⑤ 만약 Access Token이 만료되었을 경우 토큰의 만료를 알리는 응답을 보낸다.

⑥ 이 때, 클라이언트는 Access Token과 Refresh Token을 같이 담아 발급 요청을 보낸다.

⑦ Refresh Token을 확인한 후 Access Token을 발급하여 Access Token을 응답과 함께 보낸다.

※ Access Token과 Refresh Token
JWT 구현은 Access Token만으로도 가능하다. 다만, Access Token이 한 번 발급되면, 유효기간이 만료될 때까지 삭제가 불가하기 때문에 토큰을 탈취당했을 때 대처방법이 없다. 그래서 유효기간이 짧을수록 좋은데, 이는 사용자 불편을 초래할 수 있다. 이에 대한 해결책으로 Refresh Token을 사용한다.
Refresh Token은 Access Token보다 긴 유효기간을 가지고 있어 Access Token이 만료됐을 때 새로 발급해주는 열쇠 역할을 한다. Refresh Token도 같이 탈취될 수 있지 않냐는 의문이 생길수 도 있지만, Refresh Token은 Access Token에 비해 전송빈도가 매우 적기 때문에 탈취 위험이 적다는 점에서 보안 상의 이점이 있다.

JWT의 가장 큰 장점은 인증정보에 대한 별도의 저장소가 필요없다는 것과 서버를 Statelss하게 만든다는 것이다. 또한 모바일에서도 사용가능하다. (세션은 모바일에서 동작하지 않는다.)

JWT의 단점은 토큰에 정보를 담고 있기 때문에 토큰 탈취에 대한 대처가 어렵고, 세션관리가 이루어지지 않기 때문에 토큰을 취소하거나 사용자를 강제 탈퇴시키기 어렵다는 것이다. 또한 토큰에는 사용자 정보와 클레임이 포함되어 있어 크기가 매우 큰데, 이를 매 요청시마다 전송해야 하므로 네트워크 부하를 초래하기 쉽다.

[이미지 출처]

profile
Java Spring, Android Kotlin, Node.js, ML/DL 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

0개의 댓글