본문 바로가기
개발 이론/Spring Security

AccessToken과 RefreshToken 조금더 자세하게

by dal_been 2023. 12. 30.
728x90

이전에 AccessToken과 RefreshToken에 대해서 블로그를 쓴적이 있지만 자세하게 쓰지 않은 것같아서..

거기다가 부트캠프 멘토님이 토큰에 대해서 질문하시는데 머리로 어버법하다가 끝났다....ㅎㅎ

 


세션 기반 인증과 토큰 기반의 인증 차이점

 

세션 기반 인증과 토큰 기반 인증에 대해서 설명하기 이전에 HTTP에 대해 간단하게 이야기 해보자.

HTTP는 stateless라는 특징이 있다. 즉 각 요청이 이전에 수행된 작업을 전혀 인식하기 못한다. 

쉽게 이야기 하자면 내가 로그인하고 다른 페이지로 이동했을때 HTTP동작은 우리가 아까 로그인했는지 모른다. 

 

그렇다면 사용자의 아이디와 비밀번호를 브라우저에 저장해놓고 매 요청마다 그 정보를 보내야할까??

안된다. 일단 민감한 데이터가 클라이언트에 저장되어있기 때문에 보안적으로 취약하고 서버 입장에서도 매번 데이터베이스를 통해 조회하고 인증과정을 거쳐야하기 때문에 비효율적이다.

 

그래서 나온데 세션, 토큰 기반 인증이다

 

세션 기반 인증

https://hackernoon.com/using-session-cookies-vs-jwt-for-authentication-sd2v3vci

 

세션은 쿠키를 이용해서 session-id를 클라이언트쪽에 저장한다. 또한 세션에 대한 정보는 서버쪽에 있기 때문에 서버 자원을 활용한다.

 

세션기반 인증은 사용자 인증정보가 서버의 세션 저장소에 저장된다.

1. 사용자 로그인

2. 해당 인증 정보 세션에 저장 + 사용자에게 저장된 세션 정보 식별자 session-id발급

 → 발근된 session-id는 클라이언트 블라우저에 쿠키형태로 저장되고, 실제 인증정보는 서버의 세션에 저장되어있다

3. 이후 요청마다 Http Cookie헤더에 session -id를 함께 서버로 전송

4.서버는 요청을 바탕으로 session-id에 해당하는 세션정보가 세션 저장소에 존재하면 해당 사용자를 인증된 사용자라고 판단한다

 

한마디로 정리하자면 서버에 인증정보를 저장한다.

 

 

토큰기반 인증

https://hackernoon.com/using-session-cookies-vs-jwt-for-authentication-sd2v3vci

 

토큰 기반 인증은 인증 정보를 클라이언트가 직접 들고 있는 것이다.

이때 인증정보는 토큰 형태로 클라이언트의 로컬스토리지(쿠키)에 저장된다.

 

1. 사용자 로그인

2. Jwt토큰 발급하여 서버에서 보냄

3. 이후 요청마다 클라이언트가 가지고 있는 토큰을 Http Authorization 헤더에 실어 보낸다.

4. 서버는 요청에서 보낸 토큰을 바탕으로 위변조, 만료시각을 확인하고 토큰에 담겨있는 사용자 인증정보를 확인해 사용자를 인가한다.

 

한마디로 정리하면 서버에 정보를 저장하지 않는다.

 

세션 기반 인증과 토큰 기반 인증 비교해보자

 

1. 서버의 부담

세션기반 인증 방식은 세션 데이터를 직접 저장하고 관리한다. 즉 세션의 데이터양이 많아지면 서버의 부담이 증가할 것이다.

그러나 토큰 기반 인증 방식은  클라이언트 쪽에서 인증데이터를 가지고 요청마다 보내준다. 

 

2. 확장성

서버 확장에는 수평확장과 수직 확장이 존재한다. 간단하게 말하자면  수평 확장은 여러대의 서버를 추가로 설치하는 것이다. 하나의 서버가 처리하던 일을 여러대의 서버가 처리하는 것이다. 수직확장은 스펙이 좋은 단일 서버로 더 좋게 업그레이드 하는 것이다. 예를 들어 cpu를 조금더 좋은 제품으로 교체하는 것이다. 

 

일반적으로 웹 어플리케이션에서는 수평확장을 사용한다. 즉 여러대의 서버가 요청을 처리하게 되는데 세션기반 인증 방식은 세션 불일치 문제를 겪게 된다. 그러나 토큰 기반 인증 방식은 서버가 인증 정보를 저장하는게 아니라 클라이언트로부터 오는 정보를 가지고 인증하기 때문에 Http의 statelss 특징을 활용할 수 있다. 

 

세션 불일치
-> A서버에 로그인 요청을 했다면 유저의 세션정보는 A서버에 생성된다. 하지만 해당 유저가 A서버가 이용할 것이라는 보장은 없다. 만약 B서버에 다른 페이지에 대한 요청을 했다면 B서버에는 유저의 세션 정보가 업기 때문에 요청이 제대로 처리되지 않는다 

해결방법은 sticky session, session clustering, 세션 스토리지 분리 가 있다. 이부분에 대해서 나중에 블로그에 올리도록 하겠다.

 

 

3. 크기?

세션의 경우 cookie 헤더에 session-id만 보내면 되지만 jwt같은 경우 인증 정보, 토큰 만료시간, id등 session-id비해 정보가 많다.

 

이 게시글을 보면  바이트의 크기가 다르다

 

If we store the ID in a cookie, our total size is 6 bytes. 
If we store the ID in a JWT (with basic header fields set, as well as a reasonably long secret), 
the size has now inflated to 304 bytes. For storing a simple user session,
that is a ~51x size inflation on every single page request in exchange for cryptographic signing (as well as some header metadata).

 

그니까 session id의 경우 6바이트지만 jwt의 경우 304바이트가 나왔다고 한다. 이런 50배 정도의 차이로 인해 트래픽이 비효율적이다.

 

4. 보안문제?

토큰기반인증의 토큰의 경우 서버가 트래킹하지 않는다(서버가 해당 정보를 저장하지 않는다) 즉 클라이언트 쪽에서 모든 인증정보를 가지고 있다. 만약 한번에 해커에게 탈취당한다면 해당 토큰이 만료되기전까지는 피해를 입을 수 밖에 없다.

 

그러나 세션의 경우 모든 인증 정보를 서버에서 관리하기 때문에 보안측면에서 조금 유리하다. 만약 session-id가 탈취된다고 해도, 서버측에서 해당 세션을 무효화처리하면 된다.

 

 


 

AccessToken

왜 AccessToken은 Jwt를 사용할까??

 

 무상태 및 확장성이다.

토큰을 활용함으로써 HTTP의 특징인 stateless하게 하여 서버 확장에 적합하다. 

즉 서버쪽에서 해당 토큰에 대한 상태,정보를 보관하고 있지 않다.

(만약 세션기반이라면 세션 불일치 문제를 해결해야한다)

 

AccessToken은 클라이언트 쪽에서 어디에 저장할까?

 

1. 로컬 스토리지 또는 세션 스토리지

로그인 성공시 받아온 토큰을 세션 스토리지에 저장한뒤 , 요청을 보낼때 자바스크립트로 꺼내서 보낸다고 한다.

다만 이 방식은 로컬,세션 스토리지에 저장함에 있어 XXS(Cross Site Scripting) 공격에 취약하다고 한다. 뭐 React를 사용하면 XSS공격을 막아준다고는 하는데 항상 통하는게 아니라고 한다.

 

2. Http only 쿠키

Http only 옵션을 통해 자바스크립트 접근을 막을 수 있다고 한다. 또한 Https 가 적용되지 않으면 쿠키를 탈취할 수 있어서 secure옵셥을 통해 쿠키가 HTTPS연결에서만 전송되도록 지정되어야한다고 한다.

이렇게 되면 탈취 위험, 자바스크립트 쿠키 취득을 막을 수 있다.

 

그렇다고 해서 쿠키가 무한정으로 안전할까?? 아니다. 쿠키에 토큰을 담으면 CSRF공격에 취약하다고 한다. XSS는 토큰값 자체를 가져오는 것이라면 CRSF는 로그인 된 상태로 특정 위험한 동작을 하게 만든다??의 의미라고 한다. 

 

음 그래서 어디에 저장해야하는데?? 사실 프론트는 잘몰라서... 찾아보다가 어떤 분 블로그에 의하면 자바스크립 private변수로 저장하는 방법이 있다고한다. 이렇게 되면 XSS,CSRF공격을 당할 가능성이 없다고 한다. 

(근데 페이지 이동마다 토큰이 날라간다는데 이부분은 React의 SPA??를 이용하면 된다고 한다)

 

 

 

그런데 만약 AccessToken이 탈취된다면 어떻게 될까?? 토큰이 만료될때까지 기다려야한다...(토큰을 별달리 무효화시키는 방법이 없기 때문)

음... 속수무책이군.. 그래서 나온게 Refresh Token이다.

 

 

RefreshToken

AccessToken유효기간을 짧게 함으로써 자주 재발급하게 하여 보안 강화 + AccessToken만료로 잦은 로그아웃 경험 막기

 

AccessToken은 유저에 대한 인증을 위해 리소스 접근 토큰이라면 RefreshToken은 AccessToken 재발급을 위해 사용된다.

RefreshToken은 AccessToken에 비해 긴 유효시간을 가진다. 일반적으로 RefreshToken을 2주정도로 설정하다고 한다.(

 

RefreshToken은 Jwt?? 문자열(UUID)??

 

JWT라면 stateless하고 토큰에 데이터를 담을 수 있다. 그렇기 때문에 데이터베이스에 별도로 접근하지 않아도 되어 서버의 부하가 상대적으로 적을 것이다.

다만 AccessToken과 마찬가지로 서버에서 제어할 수 없기 때문에 탈취당한 상태에서 토큰을 무효화시킬 방법이 없다

 

문자열,UUID라면 해당 토큰을 사용자임을 데이터베이스에 저장해야한다. 물론 데이터베이스에 접근해야하는 부분이 존재하지만 탈취되었을때 무효화 시키거나 차단하거나 할 수 있다.

 

 

그래서 RefreshToken은 어떻게 저장되는 거야?

 

나의 경우 AccessToken 재발급 + 무효화처리를 위해 데이터베이스를 이용했다.

즉 UUID나 문자열을 사용하게 된다면 클라이언트 쪽에서는 인덱스 값을 쿠키나 로컬스토리지에 저장한다.

쿠키로 저장한다면 만료기간을 조금 길게 잡는다면 로그인 상태를 유지할 수 있고 인덱스 값을 해시로 생성하여 사용하면 보안 측면에서 더 유리할 수 있다. 

 

그래서 RefreshToken에 대한 정보를 데이터베이스에 저장하면서 클라이언트쪽에서는 인덱스 값을 쿠키나 로컬스토리지에 저장한다

 

 

RefreshToken 어떤 종류의 DB에??

 

개인적으로는 RDB나 Redis중 Redis가 좋은 방향성 인것같다.

첫번째로 데이터가 HDD,SDD에 저장하는게아니라 RAM에 저장하므로 영구 저장은 불가능하지만 빠른 액세스가 가능하다.

 

두번째로 만료기간 설정이 가능하다. 만약 RDB를 사용한다면 스케줄러를 통해 만료가 되었는지 확인하고 제거하는 작업을 거쳐야한다. 그러나 redis는 유효기간을 지정가능하다. (만약 redis에서 제거되어도 로그아웃되는 정도의 단점밖에 없는 것같다)

 

 

그런데 만약 Refresh Token 탈취되면..?

 

완벽한 방법은 아니지만 RTR(Refresh Token Rotation)을 활용한다. 새로운 AccessToken을 발급받을때 RefreshToken도 새롭게 발급해주는 것이다. 즉 이미 사용된 RefreshToken을 검사하여 서비스 측에서 탈취를 확인할 수 있다.

 

다만 앞서 말했듯 완벽한 방법이 아니다. 아직 사용하지 않은 RefreshToken을 훔쳐사용할 수도 있고, 살아있는 AccessToken을 탈취할 수 있다(RefreshToken은 AccessToken을 재발급하기 위한용도)

 

 

Refresh Token 한계

 

따라서 Refresh Token의 한계를 정리해보자면...

 

첫번쨰 유효기간을 짧게 AccessToken을 한다고해서 그 짧은 시간동안 탈취될수 있다.

두번째 만약 사용되지 않는 RefreshToken을 탈취당한다면..? 해커는 AccessToken을 발행할 수 있다. 

세번째 Refresh Token를 쿠키또는 로컬스토리지에 저장한다면 앞서 얘기한 XSS, CRSF공격에 당할 수 있다.

 

 

 

https://hudi.blog/refresh-token/

https://velog.io/@ohzzi/Access-Token%EA%B3%BC-Refresh-Token%EC%9D%84-%EC%96%B4%EB%94%94%EC%97%90-%EC%A0%80%EC%9E%A5%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C

https://doogle.link/jwt-%ED%98%B9%EC%9D%80-oauth2-%EC%9D%98-refresh-%ED%86%A0%ED%81%B0%EC%9D%84-%EC%96%B4%EB%94%94%EB%8B%A4-%EC%A0%80%EC%9E%A5%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C/