티스토리 뷰

 
 

이번 포스팅은 사이드프로젝트에서 로그인 구현에 사용한
Spring Security와 JWT 사용기에 대해 정리해보려 합니다.

 
 

 

1. 세션방식이 아닌 JWT(JSON Web Token)를 사용하게 된 이유

  • 현재 사이드 프로젝트는 flutter로 클라이언트 개발을 하고 있습니다. 브라우저와 달리 모바일 앱은 쿠키가 존재하지 않습니다. 따라서 별도의 암호화된 저장소를 사용해 세션을 저장해야 합니다. 이 말은 즉, 개발자가 직접 세션 관리를 해야 한다는 의미입니다. 직접 세션을 관리할 경우 코드 상 오류가 발생하기 가능성이 높다고 생각합니다.
    (참고 각 SharedPreferences - Android / UserDefaults - ios에 세션을 저장할 수 있습니다.)
  • 모바일 환경에서 사용자는 로그인 상태를 오랫동안 유지할 것을 기대합니다. 세션 방식 로그인은 일정 시간이 지나면 만료되므로, 사용자는 자주 로그인을 해야 해 UX 관점에서 좋은 설계는 아니라고 생각합니다.
  • 서버와 클라이언트 간 상호 독립적인 개발 환경을 가지고 있어, stateless 한 토큰 기반 인증 방식이 적절하다고 생각합니다.

 

 

2. 개발 환경

Java : 21
Spring boot : 3.2.0
Spring Security : 3.2.0
JsonWebtoken : 0.11.5
redis : 6.2.5

 
 

3. 토큰 생성 및 검증 프로세스

1) 토큰 생성 프로세스

토큰 생성 프로세스 도식화
  1. 앞서서 정상적으로 로그인이 완료된 상태입니다.
  2. 클라이언트에서 토큰 요청을 보냅니다.
  3. Security 필터를 통과한(해당 url은 web.ignoring()에 설정) 토큰 요청은 JWT 라이브러리를 이용하여 Access Token과 Refresh Token을 생성합니다.
  4. Refresh 토큰은 Redis에 요청 유저명과 함께 저장됩니다.
  5. 생성된 Access Token과 Refresh Token을 클라이언트에 반환합니다. 
  6. 클라이언트는 Access Token과 Refresh 토큰을 저장하고, 이후 모든 api 요청 시 Http.Header에 토큰을 담아 서버에 요청합니다.

 

2) 토큰 검증 프로세스

  1. 클라이언트는 Access Token을 요청 헤더에 담아 서버에 요청합니다.
  2. Spring Security에 적용한 필터를 통해 해당 토큰을 검증합니다.
  3. 정상적인 토큰일 경우 이후 서비스 로직을 수행하고 200 status와 함께 클라이언트에 반환합니다.
  4. 비정상적인 토큰일 경우 서버에서는 401 status를 클라이언트에 반환합니다.

 
 

3) 토큰 재발급 프로세스(Refresh 토큰이 만료되지 않은 경우)

  1. 클라이언트는 토큰 재발급 요청을 보냅니다.
  2. 서버는 토큰을 검증합니다. (Access 토큰과 Refresh 토큰 모두 검증)
  3. Access 토큰이 만료되었지만, Refresh 토큰이 만료되지 않았다면,
    서버는 클라이언트에서 보낸 Refresh 토큰과 Redis에 저장한 Refresh 토큰을 비교합니다.
  4. 비교한 Refresh 토큰이 동일하다면, 서버는 새로운 토큰(Access 토큰, Refresh 토큰)을 생성하고 이를 클라이언트에 전달합니다.

 

4) 토큰 재발급 프로세스(Refresh 토큰이 만료된 경우)

  1. 클라이언트는 토큰 재발급 요청을 보냅니다.
  2. 서버는 토큰을 검증합니다. (Access 토큰과 Refresh 토큰 모두 검증)
  3. 두 토큰 모두 만료되었다면, 서버는 401 status를 클라이언트에 반환합니다.
  4. 클라이언트는 로그인 화면으로 강제 라우팅 시킵니다.

 
 
 

3. 느낀 점

JWT를 왜 쓰는가?

JWT의 대표적인 장점으로 서버를 무상태(Stateless)로 서비스를 유지할 수 있고, 확장성이 우수하다는 점인데 왜 이러한 점이 장점이 되는 건지 궁금했었습니다. Security와 JWT을 이용한 토큰 기반 인증을 구현해 보면서 해당 장점을 눈으로 직접 보며 이해할 수 있어 좋은 경험이 되었습니다.  JWT를 사용하는 이유를 개인적으로 아래와 같이 정리해 봤습니다.
 
세션 방식의 경우 클라이언트에서 매번 호출할 때마다 인메모리에 올라가 있는 세션 저장소에 저장된 세션과 비교해야 하니 메모리를 계속해서 사용 중인 것이고, 사용자가 100명, 1000명, 10000명 늘어날 때마다 해당 유저의 세션을 저장소에 계속해서 저장해야 하니(stateful) 메모리 사용율이 계속해서 증가해 서버에 부하를 많이 줍니다. 이 말인 즉, 사용자가 늘어날 때마다 서버가 계속해서 늘어나야 한다는 것입니다.
 
반면에 토큰 기반 인증의 경우, 서버는 클라이언트 호출 때마다 토큰을 검증만 하기 때문에 세션 저장소가 필요하지 않아 stateless 하게 서비스를 제공할 수 있습니다. 사용자가 아무리 늘어난다고 해도 인메모리 형식의 세션 저장소에 추가적으로 저장하는 것이 없기 때문에 메모리 사용율이 증가하지 않습니다. 이를 통해 사용자가 증가한다고 해서 서버를 늘릴필요는 없는 것입니다. 이러한 부분이 확장성이 우수하단 이유가 되는 것입니다.
 

토큰 인증 방식과 세션 인증 방식의 보안적인 측면에서 어떻게 대처할 것인가?

1) 세션이 탈취당했다면 서버에서는 어떻게 대처해야 하는가?
세션 저장소에 탈취당한 유저의 세션을 삭제하는 것입니다. 세션 인증 방식의 경우 인메모리에 세션 저장소에 사용 중인 유저의 세션 정보를 가지고 있기 때문에 탈취당한 세션 정보를 지워버린다면 탈취당한 세션으로 요청을 보내도 서비스 이용을 강제로 막을 수 있습니다.
 
2) 토큰이 탈취당했다면 서버는 어떻게 대처해야 하는가?
Access Token과 Refresh 토큰을 모두 탈취당했다면 서버에서는 Refresh 토큰을 저장한 DB에 해당 토큰을 삭제할 수 있습니다. 
또한, Access Token 만료 기한을 짧게 설정해 토큰이 탈취당했더라도 만료 기한을 넘어가면 서비스를 정상적으로 이용할 수 없습니다. 이와 같은 조치로 피해를 최소화할 수 있습니다.
 
 
 

 
이상 전달 끝!

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함