본문 바로가기

Frontend/ETC

[WEB] 로그인 인증 방식 (Session & JWT)

반응형

개요

웹 사이트를 만들면서 로그인은 빼놓을 수 없는 기능이다. 그런데 내가 이 로그인 방식을 잘 모른다는 생각이 들었다. 구글이나 네이버, 카카오 등 다양한 소셜 로그인은 프론트엔드만으로도 뚝딱 구현할 수 있지만, 그 내부 동작은 사실 잘 모르고 사용했다.

 

이번 기회에 로그인에 대한 모든 것을 알아보기로 했다. 백엔드와 인증 정보를 어떻게 주고받는지, 브라우저에서 그 인증 정보를 어디에 담아야 할지 전체적인 흐름을 기반으로 알아보자!

 

로그인 흐름

기본적인 로그인의 흐름은 다음과 같다.

  1. 사용자가 아이디와 비밀번호를 입력하고 로그인 버튼을 클릭
  2. (클라이언트) 로그인 API를 서버에 요청
  3. (서버) 인증 정보를 응답으로 전송
  4. (클라이언트) 받은 인증 정보를 저장
  5. (클라이언트) 이후 API 요청 시 인증 정보를 함께 전송
  6. (서버) 사용자 인증을 검증하고 응답

 

 

인증 방식

위 흐름에서 브라우저가 로그인 API를 호출했을 때, 서버에서 인증을 처리하는 방식 두 가지가 있다. 그리고 이 인증 방식에 따라 클라이언트에서 인증정보를 처리하는 방식이 달라진다.

 

💡 사전지식

  • HTTP 프로토콜은 Stateless하다.
  • 따라서 클라이언트는 서버에 요청할 때마다 클라이언트의 정보를 보내줘야 한다.

 

1. 세션 기반 인증

첫 번째는 전통적인 방식으로 볼 수 있는 세션 기반 인증이다.

 

  1. 클라이언트에서 로그인 API를 요청하면
  2. 사용자의 자격 증명을 확인한다. (아이디와 비밀번호가 DB상의 정보와 일치하는지 체크)
  3. 인증에 성공하면, 서버는 sessionID를 생성하고 (세션을 생성하는 것)
  4. 서버 메모리나 데이터베이스에 생성한 sessionID를 저장한다.
  5. 그리고 Set-Cookie 설정을 통해 sessionID를 클라이언트에 전달한다.
  6. (이때 httpOnly 속성을 추가하면 브라우저에서 쿠키에 접근할 수 없게 된다.)
  7. 클라이언트는 서버로부터 받은 sessionID를 쿠키에 저장한다.
  8. 이후 인증이 필요한 API를 호출할 때 쿠키가 자동으로 포함되기 때문에, sessionID가 자동으로 전달된다.
  9. 서버는 클라이언트에서 넘긴 쿠키의 sessionID를 조회(DB 조회)해서 올바른 사용자인지 식별한다.

 

💡 sessionID

  • 로그인한 사용자의 정보를 식별하는 키
  • 로그인한 사용자의 세션을 유지하기 위한 값
  • 서버에서 관리되는 고유한 값

 

💡 클라이언트에서 뭘 해야 할까?

  • sessionID는 서버에서 관리되기 때문에 클라이언트에서 뭔가 딱히 할 것이 없다.
  • 서버에서 Set-Cookie 헤더로 쿠키를 보내면, 브라우저가 자동으로 이를 저장한다.
  • 다만, API 요청 시 쿠키 활성화를 위한 credentials: 'include' 설정을 추가해야 한다. (axios의 경우 withCredentials: true 옵션을 설정)
  • 요점은 클라이언트에서 직접 다루지 않는다는 것!

 

장점

  • 보안성: 서버에 sessionID를 저장하기 때문에 클라이언트에서 조작이 어렵다.
  • 유효성 관리: 서버에서 세션 만료 시간이나 로그아웃 등을 직접 관리할 수 있다.
    • ex) 다중 디바이스에 로그인된 경우, 원격으로 로그아웃 처리할 수 있다.

 

단점

  • 서버 부담: 유저가 증가할수록 서버(메모리) 부담이 증가한다. (유저별로 sessionID를 저장해야 하니까)
  • 확장성: 여러 서버(분산 환경)에서 세션 공유를 위한 별도의 설정이 필요하다. → 복잡성 증가
  • 쿠키 의존성: 클라이언트가 쿠키를 비활성화하면 세션 인증이 동작하지 않는다.

 

2. 토큰 기반 인증

두 번째는 JWT(Json Web Token)로 흔히 알려진 토큰 기반 인증이다.

*엄밀히 말하면 토큰 인증 방식 중 하나가 JWT지만 여기서 토큰은 JWT라고 가정한다.

 

  1. 클라이언트에서 로그인 API를 요청하면
  2. 사용자의 자격 증명을 확인한다. (아이디와 비밀번호가 DB상의 정보와 일치하는지 체크)
  3. 인증에 성공하면, 서버는 사용자 정보를 기반으로 토큰을 생성한다.
  4. 그리고 클라이언트에 해당 토큰을 전달한다.
  5. 클라이언트는 localStorage, sessionStorage, 또는 Cookie에 토큰을 저장한다.
  6. 이후 클라이언트는 인증이 필요한 API를 호출할 때 토큰을 Authorization 헤더에 포함해야 한다.
  7. 서버는 각 API 요청에 있는 토큰을 검증해서 올바른 사용자인지 식별한다.

 

💡 JWT

  • 인증에 필요한 정보를 토큰에 담아 암호화한 값
  • 로그인한 사용자의 정보를 포함하는 디지털 서명된 문자열
  • 클라이언트에서 관리하는 고유한 값

 

💡 클라이언트에서 뭘 해야 할까?

  • 토큰을 스토리지나 쿠키로 관리해야 한다. (일반적으로 스토리지에 저장)
  • 로그인 성공 시 토큰을 저장하고, 로그아웃 시 토큰을 삭제해야 한다.
  • 인증이 필요한 요청마다 헤더에 Authorization: Bearer <token> 추가해야 한다.

 

장점

  • 확장성: 서버가 클라이언트의 상태를 유지하지 않으므로 분산환경에 적합하다.
  • 무상태성: 매 요청마다 인증 정보(토큰)를 포함하므로 서버가 세션을 저장하지 않아도 된다. → 서버 부담 X
  • 빠른 인증: JWT는 사용자 정보를 포함하기 때문에 별도의 DB 조회 없이 사용자를 확인할 수 있다.

 

단점

  • 보안성: 클라이언트가 토큰을 안전하게 저장하지 않으면 유출의 위험이 존재한다.
  • 만료 관리: 토큰이 만료되기 전에 유출되는 경우, 재발급 로직이 필요하다.
  • 토큰 크기: 토큰 크기가 커질 경우, 네트워크 대역폭 사용량이 증가할 수 있다.

 

토큰이 탈취당하면?

세션 기반 인증 방식의 가장 큰 단점은 서버 상태를 유지해야 하므로 확장에 어려움이 존재한다는 것이다. 반면 JWT 기반 인증 방식은 서버가 사용자의 상태를 유지하지 않아 스케일링에 유리하지만, 이러한 무상태의 특성 때문에 서버가 토큰을 무효화할 수 없다는 단점이 있다. 토큰이 탈취당해도, 클라이언트에서 가지고 있는 토큰이 만료되기 전까지 막을 수 있는 방법이 없다는 것이다.

 

보완 방법: Refresh Token 활용

지금까지 다뤘던 토큰은 AccessToken이고, RefreshToken이라는 개념이 등장한다.

  AccessToken RefreshToken
개념(목적) API 요청 인증에 사용되는 토큰 AccessToken을 갱신해주는 토큰
수명 짧다. (n분 ~ n시간) 길다. (며칠 ~ 몇 주)
저장 위치 localStorage, sessionStorage HttpOnly 쿠키
서버 저장 X O

 

 

흐름

 

  1. 클라이언트에서 로그인 API를 요청하면
  2. 사용자의 자격 증명을 확인한다. (아이디와 비밀번호가 DB상의 정보와 일치하는지 체크)
  3. 인증에 성공하면, 서버는 사용자 정보를 기반으로 AccessTokenRefreshToken을 발급한다.
  4. 서버는 클라이언트에 발급한 토큰을 전달하고, DB에 RefreshToken을 저장한다.
  5. 클라이언트는 AccessToken은 스토리지에, RefreshToken은 쿠키에 저장한다.
  6. 이후 클라이언트는 인증이 필요한 API를 호출할 때 토큰을 Authorization 헤더에 포함해야 한다. RefreshToken은 쿠키에 자동으로 포함된다.
  7. AccessToken이 만료되었으면
  8. 서버는 401 응답을 반환하고
  9. 클라이언트는 토큰 갱신을 요청한다.
  10. 서버는 RefreshToken을 검증한다.
  11. DB에서 RefreshToken이 올바른지 확인하고
  12. 유효성 검증이 완료되면 새로운 AccessToken을 발급해서 클라이언트에 전달한다.
  13. 유효성 검증에 실패하면 RefreshToken을 삭제하고 클라이언트에 401 에러를 반환하면
  14. 클라이언트는 로그아웃 처리를 진행한다.

 

💡 Redis

  • 초고속 인메모리 key-value 저장소
  • 디스크가 아니라 메모리(RAM)에 데이터를 저장하는 스토리지로 조회/저장이 빠르다.

 

결론

  • RefreshToken을 서버에서 관리함으로써 토큰이 탈취를 예방할 수 있는 것이다.
  • 의심되는 상황에서 DB의 RefreshToken을 삭제하면 되기 때문이다.
  • 따라서 AccessToken의 만료시간을 짧게 가져가고, RefreshToken으로 이를 계속 갱신하는 것을 최선의 방법이라고 볼 수 있다.

 

의문점

Q. 토큰 기반 인증 방식의 장점이 스케일링인데, RefreshToken을 DB에 저장하면 세션 기반 방식과 다른 점이 무엇일까?

A. RefreshTokensessionId 보다 최소한의 정보만을 가지고 있으며, 모든 요청마다 세션을 조회하는 것이 아니라, AccessToken 인증 실패 시에만 RefreshToken을 조회한다는 차이점이 있다. 따라서 Stateless를 유지하며, 예외적으로만 상태를 사용한다는 점에서 세션 방식보다 가볍고 확장성이 높다고 볼 수 있다.

 

 

반응형

'Frontend > ETC' 카테고리의 다른 글

[도서] UX/UI의 10가지 심리학 법칙 2  (0) 2024.02.15
[도서] UX/UI의 10가지 심리학 법칙 1  (0) 2024.02.08