[Spring security] - JWT와 Redis

yeom yaloo·2023년 6월 6일
2
post-thumbnail

1. JWT?

1.1 도입하게 된 계기

  • 기존 http의 경우엔 보안을 위해서 stateless 한 방식을 사용함에 통신이 끊기면 계속해서 추가로 입력했던 정보를 다시 요구하기 때문에 이에 불편함을 느끼게 됐다.
    • 이것의 단적인 예로 로그인을 했다가 서버를 끄고 켰을 때 다시 로그인 작업을 해야하는 등의 불편함이 이에 해당한다.
  • 이를 해결하기 위해서 http에서 조금 더 안전하게 stateful한 상태를 유지하고자 고민을 하던 도중 session, cookie, jwt 라는 방식을 알게 되었고 이들의 장단점을 찾아보고 조금 더 안전한 페이지를 만들고자 jwt를 도입하게 됐다.

1.2 Session을 대신해서 이를 사용하는 이유

  • cookie는 보안상 이슈가 너무 크기 때문에 고려하지 않았다.
  • session의 경우 서버 메모리에 해당 session Id를 저장하는 방식을 사용하는데 이는 많은 회원이 접속할 수록 문제가 되기 때문에 이 문제를 처음부터 없애고자 하였다.
  • 트래픽 증가에 따라서 로드 밸런서를 사용해 서버를 여러대로 구성하게 될 때 session 자체는 각 서버에 공유되지 않기 때문에 원래의 취지인 상태 유지를 위한 방법이 되지 않는다고 판단 하였다.
    • redis를 사용하면 되었겠지만 그래도 jwt를 사용해보고 싶었고, 모바일 환경까지 고려한다면 jwt 선택이 더 나은 선택지라고 보여서 이를 선택해서 사용하기로 했다.
    • 물론 서비스 규모가 작고 트래픽이 그리 높지 않은 서비스라면 서버 한 대로도 충분하지만 조금 더 깊이 있게 공부하고 싶어서 세션보다 더 나은 선택지를 찾기로 했다.

2. JWT의 장단점

2.1.1 장점

  • 가볍다.
    • 따로 서버에 메모리에 저장하는 방식인 세션과 다르게 JWT의 경우엔 서버 메모리 확보가 필요하지 않다. 클라이언트가 서버에서 보내준 토큰을 가지고 있다가 요청이 올 때 헤더에 해당 토큰을 보내주면 토큰 검사를 통해서 이를 확인하고 작업한다.
  • 모바일 환경에서 최적화 되어있다.

2.1.2 단점

  • 페이로드 크기가 커지면 전송 비용이 커진다.
  • 자동 소멸이 되지 않기 때문에 계속 냅두게 되면 해킹의 위험성이 높아진다.
    • 페이로드는 로그인 유저임을 증명할 수 있는 기본적인 정보들을 말한다.
  • localstorage에 jwt 을 보관하면 xss공격에 취약하게 된다.

3. jwt의 치명적 문제 해결을 위한 Redis 도입

그래서 이런 저런 이유로 JWT를 이용해서 해당 authentication(인증) 작업을 진행할 때 JWT 도입을 하고 사용하려고 했다. 그러나 사실상 JWT는 토큰 자체가 만료가 되어도 삭제가 되지 않는 문제가 있기 때문에 이 문제를 어떻게 해결할까 하다 찾아본 해결책이 redis 였다.

3.1 redis?

레디스는 Remote Dictionary Server의 약자로서, "키-값" 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템이다. 2009년 살바토르 산필리포가 처음 개발했다. 2015년부터 Redis Labs가 지원하고 있다.
📌출처: https://g.co/kgs/XMnBqk

3.2 jwt의 accessToken & refreshToken

  • accessToken의 경우엔 토큰 자체의 탈취 문제로 만료시간을 짧게 둔다. 그런데 상태 유지를 위해서 jwt를 사용하는데 이 방법이 만료시간이 짧아서 회원들이 자주 다시 로그인을 해야하는 등의 불편함이 생긴다면 이 방법을 적용하는 이유가 없을 것이다.
  • 이를 해결하고자 토큰 만료 시간이 짧아도, 토큰이 탈취 되어도 문제가 없도록 refreshToken을 사용해서 이 문제점을 보완해보고자 한다.
  • 이때 refreshToken을 탈취 당하면?
    • 해당 토큰을 가지고 새로운 accessToken을 발급해주는 문제가 있을 수 있는데 이때 accessToken의 클라이언트가 누구인지 알수 없다는 문제가 생길 수 있다.
    • 발급해준 refreshToken 역시 저장(=db에)해두고 비교해가며 해당 토큰이 제대로 된 토큰인지를 확인하여 refreshToken이 accessToken을 발급해줘도 소용이 없게 한다.
    • 이때 저장은 redis에서 진행될 예정

Refresh Token의 탈취
그런데 이 Refresh Token 자체가 탈취당한다면 어떻게 할까? 공격자는 이 토큰의 유효 기간만큼 다시 Access Token을 생성해서 다시 정상적인 사용자인 척 위장할 수 있다. 그렇기 때문에 여기서는 서버측의 검증 로직이 필요한데 스택오버플로우의 답변을 보면 다음과 같은 방법을 제안하고 있다.
데이터베이스에 각 사용자에 1대1로 맵핑되는 Access Token, Refresh Token 쌍을 저장한다.
정상적인 사용자는 기존의 Access Token으로 접근하며 서버측에서는 데이터베이스에 저장된 Access Token과 비교하여 검증한다.
공격자는 탈취한 Refresh Token으로 새로 Access Token을 생성한다. 그리고 서버측에 전송하면 서버는 데이터베이스에 저장된 Access Token과 공격자에게 받은 Access Token이 다른 것을 확인한다.
만약 데이터베이스에 저장된 토큰이 아직 만료되지 않은 경우, 즉 굳이 Access Token을 새로 생성할 이유가 없는 경우 서버는 Refresh Token이 탈취당했다고 가정하고 두 토큰을 모두 만료시킨다.
이 경우 정상적인 사용자는 자신의 토큰도 만료됐으니 다시 로그인해야 한다. 하지만 공격자의 토큰 역시 만료됐기 때문에 공격자는 정상적인 사용자의 리소스에 접근할 수 없다.
📌출처: https://velog.io/@park2348190/JWT%EC%97%90%EC%84%9C-Refresh-Token%EC%9D%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80

redis와 jwt?

redis 사용이유

  • redis의 장점은 I/O가 빈번한 데이터를 저장할 때 사용하기 좋다는 점과 운영 중인 웹 서버에서 key-value 형태의 데이터 타입을 처리해야 할 때 사용하기 좋다는 장점이 있다.
  • 또한 redis의 장점으로는 사용자 세션 관리인데 사용자의 세션을 유지하고 불러오고 여러 활동을 추적할 때 효과적으로 사용할 수 있다고 한다.

그럼 왜 jwt와 redis일까?

  • 가장 큰 이유는 jwt refreshToken을 해킹 당했을 때 accessToken을 재발급 할수 있는데 이때 이 accessToken의 소유자가 정당한 소유자인지를 확인하기 위해서 이를 db에 저장하여 사용해야 하는데 이때 가장 적합한 db가 redis이기 때문이다.
    • 이때 레디스가 데이터베이스가 아니라고 하는 사람도 있는데 이 부분은 나중에 더 알아보자

4. Spring Security와 JWT 적용

4.1 JWT Provider 작성

  • 토큰 생성
  • accessToken 발급
  • refreshToken 발급
  • 토큰 삭제
  • 유효성 검사

4.2 AuthenticationFilter

  • attemptAhthentication()
  • succssfulAuthentication()

4.3 AuthenticationManager

4.4 AUthenticationProvider

  • usersDetails
  • usersDetailsService
profile
즐겁고 괴로운 개발😎

0개의 댓글