oAuth 2.0 jwt

이선우·2024년 10월 1일
0

개발 관련 지식

목록 보기
12/13
post-custom-banner

이번 게시글은 개발을 하는 도중에 Spring OAuth 2.0 을 jwt 방식으로 사용하면서 겪은 시행착오에 대해서 작성하려고 한다.

우선 사용자의 인증과 인가를 처리할 때 쿠키 방식, 세션 방식, JWT 토큰 방식의 차이점에 대해서 말해보겠다

쿠키 VS 세션 VS JWT

쿠키 인증 방식

쿠키 인증 방식은 쿠키에 사용자의 인증 정보를 담아서 인증하는 방식으로 클라이언트가 인증 정보를 관리한다

대략적인 흐름은
1. 클라이언트가 서버에 첫 로그인 인증 요청을 보내면, 서버에서 응답으로 쿠키에 사용자 인증 정보를 담아서 보낸다
2. 서버에서 응답한 쿠키를 클라이언트에서 저장하고, 인증/인가 요청 시마다 서버로 요청한다

흐름이 간단해보이는 것처럼 인증/인가 작업이 가장 쉽고 간단하다. 사용자의 인증 정보를 클라이언트가 관리하기 때문에 서버 부하가 적고, 인증 상태를 서버가 관리하지 않고 매번 클라이언트의 인증 정보를 담은 쿠키의 요청을 받을 때 처리하므로 Stateless 하다.

그러나 인증/인가 작업이 간단한 만큼 가장 치명적인 단점이 존재한다

  1. 클라이언트 즉 사용자가 쉽게 쿠키에 담긴 인증 정보를 위조할 수 있다
  2. 쿠키의 데이터 크기가 제한적이고, 또 크키가 커진다면 네트워크 부하가 심해진다

CSRF, XSS 공격등에 매우 취약하기 때문에 3가지의 인증/인가 방법 중에서 가장 기초적이고 사용하지 않는 방식이다

세션 인증 방식

다음으로는 세션 인증 방식이다. 세션 인증 방식은 세션으로 사용자의 인증 정보를 관리하는 방식이다

대략적인 인증/인가 방식은 아래와 같다

  1. 클라이언트가 서버에 첫 로그인 요청을 보내면, 서버에서 인증 정보를 저장하고 SessionID를 클라이언트에게 제공한다
  2. 서버에서 받은 SessionId로 서버에게 인증, 인가 요청을 해서 서버에서 SessionID에 해당하는 인증 정보로 인증/인가를 처리한다

쿠키 인증 방식과는 다르게 사용자의 인증 정보를 클라이언트가 아닌 서버가 관리한다. 그렇기 때문에 보안상 훨씬 안전하다 추가로 세션의 장점은

  1. 한 사용자의 디바이스별 인증을 관리할 수 있다
  2. 하나의 계정 공유를 관리할 수 있다
  3. 비정상적인 접근 신고가 들어오면, 서버에서 판단하여 해당 세션을 삭제해서 바로 로그아웃시킬 수 있다

여기서 서버가 사용자 인증 정보를 관리하는 곳에 따라서도 장단점이 나뉜다.
크게 메모리, 하드디스크, DB에서 사용자의 인증 정보를 관리한다.

메모리 영역에서 사용자의 인증 정보를 관리하게 되면, 속도가 하드디스크나 DB에서 관리하는 것에 비해 빠르지만 사용자가 동시에 SessionId로 인증 요청을 많이 보내게 되면 서버 메모리가 부족해지고, 서버를 껐다 켰을 때 Session 정보가 휘발된다는 치명적인 단점이 존재한다

하드디스크에서 관리하게되면 메모리에서보다는 속도가 느리지만 DB보다는 빠르다 그리고 발생하는 문제점은 메모리도 마찬가지지만

Scale-out을 사용해서 서버가 여러 대 존재하여 로드 밸런싱을 사용할 때 한 사용자가 로그인 후 다음 인가 요청 시 로드 밸런싱으로 인해 사용자가 로그인한 서버가 아닌 다른 서버로 요청이 보내지면 다시 재로그인 필요하다.

한마디로 사용하는 서버가 여러개일때에는 인증 요청과 인가 요청 대상 서버가 달라지면 재로그인을 해야 한다는 것이다.

마지막으로 서버DB에서 사용자 인증 정보를 관리하는 방식이 있다 속도는 가장 느리지만 위에서 말한 단점들을 모두 해결할 수 있다 그렇기 때문에 세션으로 인증/인가를 진행을 해야한다면 서버 DB에 사용자 인증 정보를 관리하는 것이 가장 효율적이다.

단점은 로그인한 모든 유저의 인증 정보를 DB에 관리해야하므로 무겁고, 매 인증마다 DB를 거쳐 인증 정보를 Select 해야하므로 연결 비용이 클 수 있다.

JWT 토큰 방식

마지막으로 JWT 토큰 방식에 대해서 설명하겠다. 인증/인가 흐름은 아래와 같다
1. 클라이언트가 서버에 첫 로그인 인증 요청을 보내면, 서버에서 응답으로 Token을 담아서 보낸다
2. 서버에서 응답한 Token을 클라이언트에서 저장하고, 인증/인가 요청 시마다 서버로 요청한다

이것만 들으면 기본적으로 클라이언트가 인증 정보를 관리하기 때문에 쿠키 인증 방식과 뭐가 다르지?라는 생각이 나는 처음에 들었다. 하지만 좀 더 알아보니 차이점은 쿠키가 아닌 JWT 토큰을 매개체로 인증한다는 것과 서버에서 토큰 검증이 이루어지는 것이 쿠키에 비해서 보안적으로 안전하게 관리될 수 있는 차이점이다.

JWT는 사용자의 인증 정보와 서버의 SecretKey로 이루어져 서버가 생성한 Token이기 때문에 요청으로 온 Token이 서버에서 발급한 Token인지 검증할 때 Token의 SecretKey가 다르다면 위조, 변조된 Token이라고 판단할 수 있다

세션 방식과 비교해서는 기본적으로 클라이언트가 Token을 관리하기 때문에 서버 부하가 비교적 적다. 단점은 보안이 세션 방식 보다는 취약하고 세션 방식의 여러 기능들을 사용할 수 없다는 단점이 존재했다.

나는 현재 프로젝트에서 JWT 토큰 방식을 사용하고 있다

그래서 내가 JWT 토큰을 사용하면서 겪었던 고민을 말해보겠다
그러기 전에 우선 Access TokenRefresh Token이 뭔지 알아야한다.

Access, Refresh Token

Jwt 토큰은 유저의 신원이나 권한을 결정하는 정보를 담고 있는 데이터 조각이기 때문에 탈취를 당했을 때 Jwt 토큰을 탈취한 사람은 마치 신뢰할만한 사람인 것처럼 인증을 통과할 수 있고, 클라이언트와 탈취한 사람을 서버는 구분할 수 없기 때문에 최소한의 대비책으로 유효기간을 필수로 두어야한다.

그런데 유효기간을 짧게 두면 사용자가 로그인을 자주 해야하기 때문에 사용자 경험적으로 좋지 않고, 유효기간을 길게 두면 보안상 탈취 위험에서 벗어날 수 없다.

그래서 나온 개념이 Access TokenRefresh Token이다.

보통 Access Token의 유효기간은 1시간에서 60일 정도로 정해진 기간은 없지만 비교적 짧게 설정이 되고,
Refresh Token의 유효기간은 몇 십일에서 1년 정도를 길게 설정한다. 평소에 API와 통신할 때에는 Access Token을 사용하고, Refresh Token은 Access Token이 만료되었을 때 갱신을 하기 위해서 사용한다.

서버-클라이언트 통신 과정

좀 더 구체적으로 서버와 클라이언트가 JWT토큰을 이용해서 통신하는 과정을 말해주겠다

  1. 로그인 인증에 성공한 클라이언트는 Refresh TokenAccess Token을 서버로부터 받는다
  2. 클라이언트는 Refresh TokenAccess Token을 로컬에 저장해놓는다.
  3. 클라이언트는 헤더Access Token을 넣고 API 통신을 한다 (Authorization)
  4. 일정 기간이 지나 Access Token의 유효기간이 만료가된다.
    4.1 Access Token은 이제 유효하지 않기 때문에 권한이 없는 사용자가 된다
    4.2 클라이언트로부터 유효기간이 지난 Access Token을 받은 서버는 401 Unauthorized 에러 코드로 응답한다
    4.3 401 에러코드를 통해 클라이언트는 토큰의 유효기간이 만료되었음을 알 수 있다
  5. 헤더에 Access Token 대신 Refresh Token을 넣어서 API를 재요청한다
  6. Refresh Token으로 사용자의 권한을 확인한 서버는 응답쿼리 헤더에 새로운 Access Token을 넣어서 응답한다
  7. 만약 Refresh Token도 만료되었다면 서버는 동일하게 401 errorCode를 보내고 클라이언트는 재로그인해야한다.

그렇기 때문에 탈취자가 통신이 빈번히 일어나는 Access Token을 탈취를 하더라도 유효기간이 짧기 때문에 보안을 높일 수 있는 대안이 되는것이다

내가 여기서 고민했던 점는 Access TokenRefresh Token에 어떤 정보를 담아서 만들어야 하고, 어디에 저장을 어떻게 해야하는지였다.

우선 첫번째 후보는 로컬 스토리지 & 세션 스토리지 였다. 그러나 이렇게 저장한다면 자바스크립트로 토큰 값을 꺼내서 보내는 방식이기 때문에 XSS 공격에 취약하기 때문에 적절치 않다고 판단했다. 두번 째 후보는 쿠키이다. 쿠키 또한 자바스크립트로 접근이 가능하지만 HTTP Only 옵션과 secure 옵션을 설정한다면 보안을 높일 수 있지만 CSRF 공격에는 취약할 수 있다. 처음에는 그래도 다른 곳에 저장하는 방식에 비해서는 안전하다고 생각해 Access TokenRefresh Token을 둘 다 쿠키에 저장을 하려고 했지만 Refresh Token은 사용자의 Access Token을 재발급 하는 용도이기 때문에 괜찮을 수 있지만 Access Token 또한 쿠키에 저장한다면 CSRF 공격을 통해서 인증 인가 과정으로 보호된 동작을 실행해버릴 수 있기 때문에 Refresh Token만 쿠키에 저장하기로 결정했다

그리고 Access TokenAuthorization Header를 통해서 클라이언트에게 전달하기로 했다. 물론 완벽한 보안은 아닐 수 있지만 최소한의 위험만 감수하는 방법이라고 생각한다.

profile
백엔드 개발자 준비생
post-custom-banner

0개의 댓글