본문 바로가기
Refactoring/ReviewRanger

[리뷰레인저] JWT 토큰에 포함된 유저 정보를 제거하자 - 4

by 수짱수짱 2024. 4. 13.

해당 게시글은 프로젝트 '리뷰레인저'에 대해 개인 리팩토링 과정을 정리한 시리즈 글입니다.

이번 게시글의 주제는 'JWT 토큰의 Payload에 포함된 유저 정보로 인한 개인정보 유출 위험에 대한 개선안'입니다.


기존 로직의 문제점

위 사진은 JWT 구조를 설명하고 있다.

이 중 내용(payload)에 집중해서 보면 ‘사용자의 권한 정보’와 ‘사용자 데이터’가 들어있음을 알 수 있다

여기서 중요한 것은 아래 주의 문구를 보자

즉, 페이로드에 중요한 값을 넣어두면 누구든 디코딩을 통해 값을 열람할 수 있기 때문에 민감한 정보는 포함하면 안된다는 내용이다.

그렇다면? 지금 나의 코드는 어떠한 정보를 포함한 JWT 토큰을 생성하고 있는지 확인해보자

 

User 객체를 필드로 들고있는 모습
User Entity의 필드들, 딱 보기에도 중요한 정보들이 많다.

 

UserPrincipal은 Spring Security의 UserDetailsService를 통해 사용자를 인증하고 사용자 정보를 담는 데 사용된다
→ `loadUserByUsername()`

그래서 CustomeUserDetailsService에서 UserPrincipla을 통해 사용자를 인증하는 것이다

문제는 UserPrincipal에 User 객체가 들어있어 User의 모든 정보에 접근이 가능하다는 것이다 ;;

 

해결 방안

현재는 모든 User의 정보가 `UserPrinciple`이 가지고 있어서 외부에 이런 정보가 노출될 위험성이 매우 크다.

이렇게 된다면 `UserDetailsService`를 통해 사용자를 인증해도 들어있는 사용자의 정보는 id 밖에 없는 것이다

하지만 우리가 로그인하는 로직을 잘 살펴보면 `UserDetailsService`를 구현하여 사용할 필요가 없다

왜냐하면 단순히 입력으로 들어온 비밀번호와 db에 저장된 비밀번호가 match되는지 확인하고 토큰을 만들어주는 간단한 과정이기 때문이다.

`UserDetailsService`는 이런 로직 외에도 부가적인 것들을 구현해둔 것이기 때문에 현재의 나에겐 필요없다고 판단이 들었다.

 

따라서 해결 방안으론 외부에서 사용자에 대해 확인해도 상관없는 정보만을 UserPrincipal이 필드로 가지고있게 변경해야 한다. 동시에 로그인 로직을 단순화 시켜야 한다.

 

수정 후 코드

가장 먼저 `UserPrinciple`이 `UserDetails`를 구현하지 않게 변경했다. 이에 따라 오버라이딩 해야 하던 불필요한 메소드들도 다 지워졌다.

`UserPrinciple`은 노출되어도 상관없는 데이터인 id와 role만 가지고 있도록 변경했다.

 

`UserDetails`를 구현하지 않으면서 동시에 `CustomUserDetailsService`도 삭제했다.

 

 

login 로직은 단순히 사용자의 입력에 따라서 진행되도록 수정했다

만약 사용자가 입력한 이메일이 유효하지 않다면 유효하지 않다는 예외가 발생한다

사용자가 입력한 이메일이 유효하지만 비밀번호가 유효하지 않다면(`!passowordEncoder.matchs(userPassword, savedUser.getPassword()`) 로그인 실패 예외가 발생한다

이메일, 비밀번호가 유효하다면 권한을 만들어 권한을 바탕으로 토큰을 생성하도록 했다.

 

리팩토링 후기

사실 이번 4번 시리즈는 가장 완벽하지 않은 리팩토링이어서 추후에 또 다시 봤을 때 부족한 점이 보일 것 같다고 확신한다..

security와 jwt를 정확하게 알고 사용하지 않아서 발생한 문제라고 생각한다!

또한, security는 디버깅이 어려워서 상속받는 인터페이스나 구현체를 잘 타고 들어가야 공부하기가 쉬운 것 같다

UserPrinciple에 User타입 필드를 넣은 것은 보안상의 큰 문제가 일어날 수 있다는 것도 깨달았기 때문에 다음엔 절대~절대~ 이런 실수를 하지 않도록 유의해야겠다..!!