본문 바로가기
spring boot/인증,인가

로그인과 관련된 용어 정리 | 쿠키 | 세션 | 토큰(JWT) | stateless | 인증 | 인가 | Access token | Refresh Token

by junjunjun 2024. 5. 7.
반응형

 

팀플 중 팀원이 로그인 기능을 구현하는 과정에서 "세션 방법이 좋을까요? JWT방식이 좋을까요?"라고 물어본 적이 있었습니다.

나름 해당 개념들을 알고 있다고 생각했었는데 막상 들어오는 질문에 "요즘 JWT 방식이 많이 쓰이니까 JWT가 더 좋을 거 같아요."라고 명확한 근거 없이 대답할 수밖에 없었습니다.

 

과거의 배웠던 기억은 전부 사라지고

대강 JWT는 토큰방식이라 따로 저장소가 필요 없어 세션방식보다 더 좋고 요즘에는 JWT방식을 선호한다는 기억만이 남아있기 때문에 제대로 된 설명을 할 수 없었습니다.

 

이를 계기로 항상 헷갈렸던 로그인과 관련된 다양한 용어들에 대해 정리하는 과정을 담아보기로 결정하였습니다.


0. 사전 지식

일반적인 통신 그림

일반적으로 통신의 주체는 클라이언트와 서버이며 이 둘은 HTTP 프로토콜을 통해 데이터를 요청, 응답할 수 있습니다.

 

0. 인증 인가

- 챗 GPT의 인증 인가 설명 -
인증(Authentication)은 사용자의 신원을 확인하는 프로세스를 의미합니다. 사용자가 누구인지를 확인하고 인증하는 것입니다. 이를 통해 시스템은 사용자가 제공한 정보(예: 사용자 이름과 비밀번호)가 올바른지 확인하고, 사용자가 시스템에 액세스 할 수 있는 권한을 부여합니다.

인가(Authorization)는 인증된 사용자가 시스템의 특정 리소스 또는 기능에 대한 액세스 권한을 부여하는 프로세스를 의미합니다. 인증된 사용자가 특정 리소스에 대한 액세스 권한을 가지고 있는지 확인하는 것입니다. 예를 들어, 사용자가 로그인한 후에 특정 파일을 읽거나 쓸 수 있는 권한을 가지고 있는지 확인하는 것이 인가의 예입니다.
인증과 인가는 보안 시스템의 중요한 부분이며, 이 둘을 구별하여 사용자가 시스템에 접근할 수 있는지 여부를 결정합니다. 사용자가 인증되고 인가되었을 때만 시스템의 리소스에 액세스 할 수 있습니다.

 

개인적으로 인증은 로그인 작업, 인가는 접근 권한 작업으로 간단하게 이해하고 있습니다.

 

1. 초기 로그인 구현

초기 로그인은 어떻게 구현했을까요?

1. 로그인 요청

과정은 다음과 같습니다.

  1. 유저는 로그인을 위해 본인의 아이디와 비밀번호를 적습니다.
  2. 유저의 정보를 http에 담아 서버에 전송합니다.
  3. 서버에서는 해당 유저의 정보가 맞는지 확인합니다.
  4. 알맞은 정보임을 확인한 후 로그인 성공 응답 http를 클라이언트에 반환해 줍니다.

아주 평범한 로그인 절차 과정입니다.

하지만 만약 유저가 로그인 이후 게시글 작성을 하고 싶다면 어떻게 될까요?

2. 게시글 작성 요청

 

위 그림처럼 서버 입장에서는 어떤 유저의 요청인지 알 수 있는 방법이 없기에 요청을 받아줄 수 없습니다.

그렇기 때문에 유저는 게시글을 작성하기 위해 또다시 본인의 아이디와 비밀번호를 http 요청에 담아서 서버에 보내줘야 됩니다.

 

"아까 로그인했을 때 이미 정보를 보내줬는데 왜 서버는 모르나요?"

 

그 이유는 바로 HTTP가 stateless 한 성질을 가지고 있기 때문입니다.

stateless 말 그대로 상태를 가지고 있지 않다는 의미로 서버가 클라이언트의 상태를 저장하고 있지 않음을 의미합니다.

따라서 매번 http 요청은 개별적이기 때문에 요청할 때마다 로그인 정보를 보내줘야 됩니다.

 

반대로 stateful은 서버가 클라이언트의 상태를 저장합니다.

아래는 stateless와 stateful에 대한 예시 그림입니다.

stateless vs stateful

 

 

2. 쿠키를 이용한 로그인

초기 로그인은 매 요청마다 유저 정보를 받아야 되는 문제점이 있습니다.

이를 해결하기 위해 나온 것이 바로 쿠키입니다.

 

정의

쿠키는 웹 서버가 생성하여 웹 브라우저로 전송하는 작은 정보 파일입니다. 웹 브라우저는 수신한 쿠키를 미리 정해진 기간 동안 또는 웹 사이트에서의 사용자 세션 기간 동안 저장합니다. 웹 브라우저는 향후 사용자가 웹 서버에 요청할 때 관련 쿠키를 첨부합니다.

 

쉽게 말하면 웹 브라우저(클라이언트)에 있는 저장소입니다.

클라이언트는 http 요청 시 쿠키에 있는 데이터를 포함하여 서버에 보낼 수 있으며 반대로 서버에서 쿠키에 데이터를 넣어 응답할 수 있습니다.

 

아래는 쿠키를 이용한 로그인 상황입니다.

1. 로그인 요청

  1. 유저는 로그인을 위해 본인의 아이디와 비밀번호를 적습니다.
  2. 쿠키에 로그인 정보를 저장해 줍니다.
  3. http에 쿠키를 담아 서버에 전송합니다.
  4. 서버에서는 해당 유저의 정보가 맞는지 확인합니다.
  5. 알맞은 정보임을 확인한 후 로그인 성공 응답 http를 클라이언트에 반환해 줍니다.

기존 로그인 방식에서 쿠키에 정보를 추가하는 것이 추가되었습니다.

 

그렇다면 이후 유저가 게시글을 작성할 때는 어떻게 될까요?

2. 게시글 작성 요청

 

이미 쿠키에 저장되어 있는 유저 정보를 http와 함께 서버에 전달해 주면 됩니다.

쿠키의 등장으로 더 이상 유저는 추가적으로 로그인 정보를 입력하지 않아도 되게 됩니다.

 

하지만 여전히 문제는 남아있습니다.

사용자가 사용하는 브라우저는 서버보다 상대적으로 보안에 취약하기 때문에 유저의 민감한 정보를 넣어줄 경우 해커에게 정보를 탈취당할 수 있는 문제가 발생할 수 있습니다.

 

3. 세션을 이용한 로그인

쿠키만 사용했을 경우 악의적인 사용자가 쿠키를 탈취하게 되면 유저의 정보가 그대로 노출되는 문제점이 있습니다.

그렇다면 쿠키에 유저의 정보가 아닌 유저를 식별할 수 있는 서버와 약속된 어떤 값이 들어가면 어떨까요?

 

그래서 나온 것이 바로 세션입니다.

 

정의

세션(session)이란 웹 사이트의 여러 페이지에 걸쳐 사용되는 사용자 정보를 저장하는 방법을 의미합니다. 사용자가 브라우저를 닫아 서버와의 연결을 끝내는 시점까지를 세션이라고 합니다.

 

쉽게 말하면 현재 로그인 상태인 유저의 정보를 가지고 있는 서버에서 관리되는 저장소입니다.

즉 세션에는 현재 로그인 중인 유저의 정보가 담깁니다.

 

아래는 세션을 이용한 로그인 상황입니다.

1. 로그인 요청

  1. 유저는 로그인을 위해 본인의 아이디와 비밀번호를 적습니다.
  2. 유저의 정보를 http에 담아 서버에 전송합니다.
  3. DB에서는 해당 유저의 정보가 맞는지 확인합니다.
  4. DB에서는 알맞은 정보임을 확인합니다.
  5. 서버에서는 해당 유저를 식별할 수 있는 세션 ID를 만들어 줍니다.
  6. 세션 ID를 쿠키에 담아 클라이언트에게 반환해 줍니다.
  7. 클라이언트는 쿠키 저장소에 세션 ID값을 가지게 됩니다.

이제는 서버에서 로그인한 사용자의 정보를 관리하며, 각 사용자에게 랜덤 하게 할당된 식별 ID 값을 쿠키에 저장함으로써, 해킹으로 인해 사용자의 민감한 정보가 유출되는 문제를 방지할 수 있게 되었습니다.

 

이후 게시글 작성 시에는 어떻게 될까요?

2. 게시글 작성 요청

기존 쿠키를 이용한 로그인과 동일하게 쿠키에 있는 값을 함께 http로 요청을 하면 서버에서는 세션 id를 통해 어떤 유저인지 알 수 있게 됩니다.

 

 

세션은 현재에도 많이 쓰이는 방식으로 다양한 이점이 있습니다.

 

보안이 안정적입니다.

쿠키에는 무작위로 생성된 세션 ID 값이 포함되어 있어 해커가 쿠키 값을 탈취해도 해당 값이 어떤 정보인지 파악하기 어렵습니다. 만약 알아낸다 하더라도 서버에서 해당 세션 ID를 폐기해주기만 하면 됩니다.

 

로그인과 관련된 다양한 기능을 구현할 수 있습니다.

넷플릭스의 계정 공유나 구글의 현재 로그인된 기기정보 보기와 같이 계정과 관련된 기능을 구현함으로써 사용자 경험을 높일 수 있습니다.

 

하지만 단점도 존재합니다.

 

서버 비용이 많이 듭니다.

세션은 사용하기 위해서는 로그인 정보를 담을 수 있는 저장소가 필요합니다. 해당 저장소는 메모리가 될 수 있고 데이터베이스 혹은 캐시가 될 수 있습니다. 결국 새로운 저장 공간이 필요하기 때문에 서버 비용이 증가할 수밖에 없습니다.

 

서버 확장성이 떨어집니다.

서버가 증가할 경우 세션 저장소를 제대로 관리해주지 않으면 다양한 문제가 발생할 수 있습니다. 따라서 개발자는 이를 고려하여 세션을 관리해줘야 합니다. (물론 이를 해결할 수 있는 다양한 방안이 존재합니다.)

 

아래는 서버가 증가할 경우 세션에 의해 발생할 수 있는 문제에 대한 다른 분의 글입니다.

여러 대의 서버가 세션(Session)을 공유하는 방법

 

 

이러한 세션 방식의 단점을 어느 정도 극복하며 보안적으로도 안정한 방법이 토큰입니다.

 

4. 토큰(JWT)을 이용한 로그인 방식

토큰을 이용한 방식은 따로 저장소가 필요하지 않습니다. 따라서 서버가 증가하더라도 따로 조치를 취해주지 않아도 됩니다.

 

보통은 토큰 방식을 사용한다고 하면 JWT(Json Web Token)를 사용합니다.

JWT는 웹상에서 데이터를 안전하게 전송할 수 있는 json 형태의 토큰입니다.

자세한 설명은 해당 글에서 확인하실 수 있습니다.

 

JWT를 이용한 로그인 방식은 다음과 같습니다. 

1. 로그인 요청

  1. 유저는 로그인을 위해 본인의 아이디와 비밀번호를 적습니다.
  2. 유저의 정보를 http에 담아 서버에 전송합니다.
  3. DB에서는 해당 유저의 정보가 맞는지 확인합니다.
  4. DB에서는 알맞은 정보임을 확인합니다.
  5. 서버에서는 해당 유저를 식별할 수 있는 정보를 담은 payload와 비밀키를 이용하여 JWT를 생성해 줍니다.
  6. JWT를 쿠키에 담아 클라이언트에게 반환해 줍니다.
  7. 클라이언트는 쿠키 저장소에 JWT를 가지게 됩니다.

로그인된 유저의 정보를 담는 저장소가 더 이상 필요하지 않게 되었습니다.

 

이후 게시글을 작성할 때의 상황입니다.

2. 게시글 요청

http 요청으로 넘어온 JWT를 비밀키를 통해 유효성을 검증해 줍니다. 이 과정에서 유효한 유저인지 구별할 수 있으며 유저의 식별 정보를 얻을 수 있습니다.

 

토큰 방식은 세션 방식과 달리 새로운 저장소가 필요하지 않습니다. 따라서 서버가 아무리 많이 증설된다 하더라도 토큰의 비밀키만 넣어준다면 유저의 인증 정보를 확인하고 처리할 수 있습니다.

 

 

하지만 단점도 존재합니다.

 

로그인과 관련된 다양한 기능을 구현할 수 없습니다.

현재 로그인 중인 유저의 정보를 저장하지 않기 때문에 세션의 장점이었던 로그인 관련된 다양한 기능을 구현할 수 없습니다.

 

세션보다 보안적으로 취약합니다.

세션의 경우 세션값이 탈취당할 경우 세션 db에서 해당 값을 삭제해 주면 되지만 토큰의 경우에는 아무런 조치를 취할 수 없습니다.

 

이러한 문제를 해결하기 위해 가장 간단한 방법은 토큰의 유효 기간을 설정하는 것입니다. 토큰의 유효 기간이 지나면 해당 토큰을 더 이상 유효한 토큰이 아니게 됩니다. 

 

하지만 이 방법도 문제점을 가지고 있습니다. 유효 기간을 길게 설정하면 토큰이 탈취된 후 해당 기간 동안 아무런 보안 조치를 취할 수 없습니다. 그렇다고 유효 기간을 짧게 설정하면 유저가 자주 로그인해야 하므로 사용자 편의성이 떨어집니다.

이러한 JWT 관련된 보안 문제에 대해서는 다양한 해결 방안이 존재합니다.

그중 가장 많이 쓰이는 방식이 바로 Refresh 토큰입니다.

 

5. Refresh Token, Access Token

기존 토큰을 이용한 로그인 방식에서 토큰을 하나 더 추가한 방식입니다.

 

Access Token은 기존 방식에서 사용하던 토큰과 동일하지만 보안을 위해 유효기간을 짧게 만들어 줍니다. 그러면

유저가 자주 로그인 해야 되는 문제점이 있는데 이를 Refresh Token으로 갱신해 주면 됩니다.

Refresh Token은 유효기간을 길게 잡고 따로 db에 저장해 줍니다.

 

대략적인 흐름은 아래와 같습니다.

흐름

  1. 클라이언트는 Access Token과 함께 게시글 생성 요청을 보냅니다.
  2. 서버는 만료된 토큰으로 토큰 거부 응답을 보냅니다.
  3. 클라이언트는 Refresh Token으로 Access Token 갱신 요청을 보냅니다.
  4. 서버는 Refresh Token이 유효한지 확인한 뒤에 갱신된 Access Token을 응답합니다.
  5. 클라이언트는 갱신된 토큰으로 재요청을 보냅니다.

 

위 방식으로 기존 토큰 방식의 문제점을 어느 정도 해결할 수 있게 되었습니다.

사실 위 그림에서 DB가 없어도 시스템이 동작하는 데는 아무 문제가 없습니다. 

서버에서는 Refresh Token의 유효성만을 검사해 주면 되기 때문입니다. 그러나 왜 DB에 저장해 주었을까요?

그 이유는 바로 로그인 사용자를 관리하기 위함입니다. 
만약 Refresh Token까지 탈취당한 상황에서 DB가 없었다면 토큰이 만료될 때까지 아무런 조치를 취할 수 없습니다. 하지만 DB에 Refresh Token 값을 저장해 줬다면 탈취당한 토큰을 삭제함으로써 좀 더 보안적 이점을 얻을 수 있습니다.

반응형

댓글