[WEB] 로그인 인증 방식 (Session & JWT)
개요
웹 사이트를 만들면서 로그인은 빼놓을 수 없는 기능이다. 그런데 내가 이 로그인 방식을 잘 모른다는 생각이 들었다. 구글이나 네이버, 카카오 등 다양한 소셜 로그인은 프론트엔드만으로도 뚝딱 구현할 수 있지만, 그 내부 동작은 사실 잘 모르고 사용했다.
이번 기회에 로그인에 대한 모든 것을 알아보기로 했다. 백엔드와 인증 정보를 어떻게 주고받는지, 브라우저에서 그 인증 정보를 어디에 담아야 할지 전체적인 흐름을 기반으로 알아보자!
로그인 흐름
기본적인 로그인의 흐름은 다음과 같다.
- 사용자가 아이디와 비밀번호를 입력하고 로그인 버튼을 클릭
- (클라이언트) 로그인 API를 서버에 요청
- (서버) 인증 정보를 응답으로 전송
- (클라이언트) 받은 인증 정보를 저장
- (클라이언트) 이후 API 요청 시 인증 정보를 함께 전송
- (서버) 사용자 인증을 검증하고 응답
인증 방식
위 흐름에서 브라우저가 로그인 API를 호출했을 때, 서버에서 인증을 처리하는 방식 두 가지가 있다. 그리고 이 인증 방식에 따라 클라이언트에서 인증정보를 처리하는 방식이 달라진다.
💡 사전지식
- HTTP 프로토콜은 Stateless하다.
- 따라서 클라이언트는 서버에 요청할 때마다 클라이언트의 정보를 보내줘야 한다.
1. 세션 기반 인증
첫 번째는 전통적인 방식으로 볼 수 있는 세션 기반 인증이다.
- 클라이언트에서 로그인 API를 요청하면
- 사용자의 자격 증명을 확인한다. (아이디와 비밀번호가 DB상의 정보와 일치하는지 체크)
- 인증에 성공하면, 서버는
sessionID
를 생성하고 (세션을 생성하는 것) - 서버 메모리나 데이터베이스에 생성한
sessionID
를 저장한다. - 그리고 Set-Cookie 설정을 통해
sessionID
를 클라이언트에 전달한다. - (이때
httpOnly
속성을 추가하면 브라우저에서 쿠키에 접근할 수 없게 된다.) - 클라이언트는 서버로부터 받은
sessionID
를 쿠키에 저장한다. - 이후 인증이 필요한 API를 호출할 때 쿠키가 자동으로 포함되기 때문에,
sessionID
가 자동으로 전달된다. - 서버는 클라이언트에서 넘긴 쿠키의
sessionID
를 조회(DB 조회)해서 올바른 사용자인지 식별한다.
💡 sessionID
- 로그인한 사용자의 정보를 식별하는 키
- 로그인한 사용자의 세션을 유지하기 위한 값
- 서버에서 관리되는 고유한 값
💡 클라이언트에서 뭘 해야 할까?
sessionID
는 서버에서 관리되기 때문에 클라이언트에서 뭔가 딱히 할 것이 없다.- 서버에서
Set-Cookie
헤더로 쿠키를 보내면, 브라우저가 자동으로 이를 저장한다. - 다만, API 요청 시 쿠키 활성화를 위한
credentials: 'include'
설정을 추가해야 한다. (axios의 경우withCredentials: true
옵션을 설정) - 요점은 클라이언트에서 직접 다루지 않는다는 것!
장점
- 보안성: 서버에
sessionID
를 저장하기 때문에 클라이언트에서 조작이 어렵다. - 유효성 관리: 서버에서 세션 만료 시간이나 로그아웃 등을 직접 관리할 수 있다.
- ex) 다중 디바이스에 로그인된 경우, 원격으로 로그아웃 처리할 수 있다.
단점
- 서버 부담: 유저가 증가할수록 서버(메모리) 부담이 증가한다. (유저별로
sessionID
를 저장해야 하니까) - 확장성: 여러 서버(분산 환경)에서 세션 공유를 위한 별도의 설정이 필요하다. → 복잡성 증가
- 쿠키 의존성: 클라이언트가 쿠키를 비활성화하면 세션 인증이 동작하지 않는다.
2. 토큰 기반 인증
두 번째는 JWT(Json Web Token)로 흔히 알려진 토큰 기반 인증이다.
*엄밀히 말하면 토큰 인증 방식 중 하나가 JWT지만 여기서 토큰은 JWT라고 가정한다.
- 클라이언트에서 로그인 API를 요청하면
- 사용자의 자격 증명을 확인한다. (아이디와 비밀번호가 DB상의 정보와 일치하는지 체크)
- 인증에 성공하면, 서버는 사용자 정보를 기반으로 토큰을 생성한다.
- 그리고 클라이언트에 해당 토큰을 전달한다.
- 클라이언트는
localStorage
,sessionStorage
, 또는Cookie
에 토큰을 저장한다. - 이후 클라이언트는 인증이 필요한 API를 호출할 때 토큰을
Authorization
헤더에 포함해야 한다. - 서버는 각 API 요청에 있는 토큰을 검증해서 올바른 사용자인지 식별한다.
💡 JWT
- 인증에 필요한 정보를 토큰에 담아 암호화한 값
- 로그인한 사용자의 정보를 포함하는 디지털 서명된 문자열
- 클라이언트에서 관리하는 고유한 값
💡 클라이언트에서 뭘 해야 할까?
- 토큰을 스토리지나 쿠키로 관리해야 한다. (일반적으로 스토리지에 저장)
- 로그인 성공 시 토큰을 저장하고, 로그아웃 시 토큰을 삭제해야 한다.
- 인증이 필요한 요청마다 헤더에
Authorization: Bearer <token>
추가해야 한다.
장점
- 확장성: 서버가 클라이언트의 상태를 유지하지 않으므로 분산환경에 적합하다.
- 무상태성: 매 요청마다 인증 정보(토큰)를 포함하므로 서버가 세션을 저장하지 않아도 된다. → 서버 부담 X
- 빠른 인증: JWT는 사용자 정보를 포함하기 때문에 별도의 DB 조회 없이 사용자를 확인할 수 있다.
단점
- 보안성: 클라이언트가 토큰을 안전하게 저장하지 않으면 유출의 위험이 존재한다.
- 만료 관리: 토큰이 만료되기 전에 유출되는 경우, 재발급 로직이 필요하다.
- 토큰 크기: 토큰 크기가 커질 경우, 네트워크 대역폭 사용량이 증가할 수 있다.
토큰이 탈취당하면?
세션 기반 인증 방식의 가장 큰 단점은 서버 상태를 유지해야 하므로 확장에 어려움이 존재한다는 것이다. 반면 JWT 기반 인증 방식은 서버가 사용자의 상태를 유지하지 않아 스케일링에 유리하지만, 이러한 무상태의 특성 때문에 서버가 토큰을 무효화할 수 없다는 단점이 있다. 토큰이 탈취당해도, 클라이언트에서 가지고 있는 토큰이 만료되기 전까지 막을 수 있는 방법이 없다는 것이다.
보완 방법: Refresh Token 활용
지금까지 다뤘던 토큰은 AccessToken
이고, RefreshToken
이라는 개념이 등장한다.
AccessToken | RefreshToken | |
개념(목적) | API 요청 인증에 사용되는 토큰 | AccessToken을 갱신해주는 토큰 |
수명 | 짧다. (n분 ~ n시간) | 길다. (며칠 ~ 몇 주) |
저장 위치 | localStorage, sessionStorage | HttpOnly 쿠키 |
서버 저장 | X | O |
흐름
- 클라이언트에서 로그인 API를 요청하면
- 사용자의 자격 증명을 확인한다. (아이디와 비밀번호가 DB상의 정보와 일치하는지 체크)
- 인증에 성공하면, 서버는 사용자 정보를 기반으로
AccessToken
과RefreshToken
을 발급한다. - 서버는 클라이언트에 발급한 토큰을 전달하고, DB에
RefreshToken
을 저장한다. - 클라이언트는
AccessToken
은 스토리지에,RefreshToken
은 쿠키에 저장한다. - 이후 클라이언트는 인증이 필요한 API를 호출할 때 토큰을
Authorization
헤더에 포함해야 한다.RefreshToken
은 쿠키에 자동으로 포함된다. AccessToken
이 만료되었으면- 서버는 401 응답을 반환하고
- 클라이언트는 토큰 갱신을 요청한다.
- 서버는
RefreshToken
을 검증한다. - DB에서
RefreshToken
이 올바른지 확인하고 - 유효성 검증이 완료되면 새로운
AccessToken
을 발급해서 클라이언트에 전달한다. - 유효성 검증에 실패하면
RefreshToken
을 삭제하고 클라이언트에 401 에러를 반환하면 - 클라이언트는 로그아웃 처리를 진행한다.
💡 Redis
- 초고속 인메모리
key-value
저장소 - 디스크가 아니라 메모리(RAM)에 데이터를 저장하는 스토리지로 조회/저장이 빠르다.
결론
RefreshToken
을 서버에서 관리함으로써 토큰이 탈취를 예방할 수 있는 것이다.- 의심되는 상황에서 DB의
RefreshToken
을 삭제하면 되기 때문이다. - 따라서
AccessToken
의 만료시간을 짧게 가져가고,RefreshToken
으로 이를 계속 갱신하는 것을 최선의 방법이라고 볼 수 있다.
의문점
Q. 토큰 기반 인증 방식의 장점이 스케일링인데, RefreshToken
을 DB에 저장하면 세션 기반 방식과 다른 점이 무엇일까?
A. RefreshToken
은 sessionId
보다 최소한의 정보만을 가지고 있으며, 모든 요청마다 세션을 조회하는 것이 아니라, AccessToken
인증 실패 시에만 RefreshToken
을 조회한다는 차이점이 있다. 따라서 Stateless
를 유지하며, 예외적으로만 상태를 사용한다는 점에서 세션 방식보다 가볍고 확장성이 높다고 볼 수 있다.
'Frontend > ETC' 카테고리의 다른 글
[도서] UX/UI의 10가지 심리학 법칙 2 (0) | 2024.02.15 |
---|---|
[도서] UX/UI의 10가지 심리학 법칙 1 (0) | 2024.02.08 |