Oauth 개발과정

Mando·2024년 5월 5일
0

OAuth 왜 이렇게 개발했나요?

관련 service 클래스 소개


  • AuthFacadeService : 로그인 또는 회원가입을 처리합니다.
  • AuthService : 로그인 또는 회원가입을 처리합니다.
  • DeactivateMemberService : 로그아웃, 탈퇴를 처리합니다.
  • OauthClientService : OAuth와 관련된 로직을 처리합니다.
  • ReissueService : 재발급을 처리합니다.

AuthFacadeService


AuthFacadeService와 AuthService는 모두 로그인 또는 회원가입을 처리합니다.

하지만, 한 클래스에서 진행하지 않고 별도의 클래스로 분리하였습니다.

그 이유는 transaction과 관련있습니다.

로그인 또는 회원가입을 하기 위해서는 외부 API를 호출해야 합니다.

하지만, 외부 API는 우리가 제어할 수 있는 것이 아닙니다.

즉, 외부 API(슬랙)에서 요청에 대한 처리가 오래 걸린다면 우리 서비스는 무턱대고 기다릴 수 밖에 없습니다.

하지만, service 클래스는 transactinon(즉 데이터베이스와의 커넥션)을 처리하는 게 주 책임입니다.

하지만, 데이터베이스의 커넥션은 한정된 개수를 가지고 있습니다.

따라서 외부 API 응답 대기 시간이 길어질수록 우리 서비스에서는 데이터베이스 커넥션을 확보하지 못 해서 새로운 유저의 요청을 받을 수 없을 수 있습니다.

자세한 설명은 아래 글을 참고해주세요.

트랜잭션 내에 외부 리소스 요청이 담기게 되면 어떤 문제가 발생할까?

Q1. 한 클래스에서 private로 분리해서 @transaction 을 하면 되지 않나?

하지만, @transaction은 AOP 기반으로 동작합니다.

즉, 프록시 객체를 생성하여 프록시 객체에서 @transaction이 붙은 메서드를 호출하는 방식입니다.

하지만, private로 접근제어자를 가지고 있으면 외부 클래스에서 호출할 수 없기 때문에 private 메서드에만@transaction을 적용할 수 없습니다.

위 내용을 정리하겠습니다.

  • OauthClientService : 외부 API를 연결합니다. 데이터베이스 커넥션 풀을 잡아먹지 않아야 합니다. 즉, 트랜젝션을 적용하지 않습니다.
  • AuthService : 우리 서비스의 DB를 연결합니다. 따라서 데이터베이스 커넥션 풀이 필요합니다. 즉, 트랜젝션을 적용합니다.
  • OauthClientService : OauthClientService, AuthService에 책임을 부여합니다.
    • cf) 기본적으로 상위 메서드에 트랜젝션이 걸려있으면 하위 메서드에도 트랜젝션이 걸립니다.(default는 상위 메서드의 트랜젝션을 그대로 이어받음)
    • 따라서 만약 해당 클래스에 트랜젝션이 걸려있다면 해당 클래스에서 OauthClientService를 호출하기 때문에 OauthClientService에도 트랜젝션이 걸리게 됩니다.
    • 이는 우리가 의도하는 바가 전혀아니기 때문에 해당 클래스에는 트랜젝션을 적용하지 않습니다.

Slack에서 제공해주는 토큰을 그대로 사용하지 않고 우리 서비스(eeos)만의 토큰을 생성하는가?


Oauth 서버의 토큰을 그대로 사용하는 것은 우리 서비스가 외부 서비스(슬랙)에 너무 의존하게 된다고 판단하였습니다.

토큰은 우리 서비스에 대한 인증/인가를 하기 위함입니다.

따라서 토큰을 직접 컨트롤 할 수 있어야 하며, 토큰 전략을 결정할 수 있어야 한다고 생각합니다.

결과적으로 Slack에서 제공받은 토큰은 표시이름과 같은 Slack에서 필요한 정보를 빼오는 데만 사용하고

우리 서비스를 이용(인가)하는데는 우리 서비스에서 발급한 토큰을 이용합니다.

데이터베이스에 어떤 값을 저장하나요?


  1. OAuthId 를 저장합니다.
    1. 새로운 회원인지 기존 회원인지 판단하기 위해 OAuthId를 가지고 있어야 합니다.

    2. OAuthId 를 가지고 있다면 ⇒ 기존 회원이므로 로그인을 진행합니다.

      OAuthId 를 가지고 있지 않다면 ⇒ 신규 회원이므로 회원가입을 진행합니다.

  2. 표시 이름을 저장합니다.
    1. 현 정책 상 에코노베이션 슬랙의 표시 이름을 저장합니다.
    2. 이때, 표시 이름을 검증해야 합니다. 해당 검증 작업은 OauthClientService에서 진행합니다.

JWT 토큰을 사용하는 이유


JWT를 사용한다면 데이터베이스로터 사용자 상태를 조회하지 않아도 됩니다.

  • stateless 하다.(서버가 상태를 가지고 있지 않다.)
  • 서버의 부담이 줄어든다.

따라서 JWT와 관련된 로직을 작성할 때는 꼭 이점을 생각하시면 좋을 것 같습니다.

JWT를 사용함으로써 DB와 connection을 줄일 수 있다.

블랙 리스트 토큰의 기준


refresh token(이하 RT)의 경우 유효시간이 깁니다.

만약, 유저가 로그아웃이나 회원 탈퇴를 하기 전에

해커가 RT토큰을 탈취한다면 해커가 해당 RT토큰으로 재발급을 진행하여 계속해서 작업을 처리할 수 있습니다.

따라서 유저가 로그아웃이나 회원 탈퇴를 했다면 해당 RT 토큰을 블랙 리스트 토큰으로 지정하여

이후 해당 RT 토큰으로 재발 요청이 들어왔을 경우 유효하지 않은 토큰으로 예외를 터트려 재발급을 진행하지 못 하게 막습니다.

Q1. 그럼 왜 acceess token(이하 AT)의 경우 블랙 리스트 토큰을 따로 지정하지 않나요?

AT 토큰의 경우 굉장히 유효기간이 짧습니다.

그래도 AT 토큰도 블랙 리스트 토큰으로 지정한다면 오히려 보안에 좋은 게 아닌가요?

맞습니다.

하지만 이는 JWT 토큰의 장점(stateless)을 잃어버립니다.

인가 작업 시에 매번 DB에 커넥션 하여 블랙 리스트에 등록된 토큰인지 확인해야 하기에 JWT 토큰을 사용하는 의미가 없어집니다.(즉 이 경우는 JWT를 stateful하게 사용하게 된다.)

따라서 AT토큰은 따로 블랙리스트로 지정하지 않고 짧은 유효기간을 가도록 하였습니다.

재발급 시에는 RTR 방식을 사용한다.


Access 토큰 재발급 시 사용한 Refresh 토큰이 만료되지 않았을 지라도

현재는 Access , Refresh Token을 모두 재발급해주고 있습니다.

(이하 RTR 방식)

이 방법을 사용한 이유는 다음과 같습니다.

유효 시간이 긴 Refresh Token이 탈취가 되었을 지라도 재발급을 한다면 탈취된 토큰은 무효화가 된다.

즉, 보안상의 이유로 RTR 방식을 사용했습니다.

Stateless/stateful하게 JWT를 사용


  • 로그인 성공 이후의 모든 요청 시(statless하게 사용)
    • 서버는 서명을 확인하여 토큰이 조작되지 않았다면 토큰으로부터 사용자를 식별할 수 있는 정보를 꺼내서 사용한다.
  • 로그아웃 / 회원 탈퇴(stateful하게 사용)
    • 해당 토큰을 블랙 리스트 토큰으로 저장한다

stateful하게 사용한 경우도 있지만

  • 로그인 성공 이후의 요청이 훨씬 더 많이 일어날 것이므로 statless의 장점을 살렸다고 생각
  • 또한, 이는 trade-off로 stateful하게 사용함으로써 보안적인 측면을 살렸다고 생각한다.

회원 탈퇴 시 이벤트 발행을 사용한 이유


회원 탈퇴 시 다음과 같은 작업을 처리해야 합니다.

  • OAuth로부터 받은 정보 제거(사용자 정보 제거)
    • OAuthId
    • 표시 이름
  • 해당 사용자의 행사 참여 정보 제거
  • 해당 사용자의 RT 토큰 저장

다음과 같은 작업을 처리해야 합니다.

이중 첫 번째 작업(사용자 정보 제거)는 주 작업이지만

2-3번째 작업은 회원 탈퇴를 진행하면서 처리해야 하는 후처리 작업입니다.

따라서 서비스 의존성을 분리하기 위해서 이벤트 발행을 사용하였습니다.

Q1. 이벤트 발행으로 인한 장점

  1. 서비스 의존성 분리

  2. 후처리 작업이 늘어났을 때(예를 들어, 탈퇴 하는 회원이 생성한 프로그램을 제거해야 한다면)

    이벤트 발행 전 : 의존해야 하는 서비스가 늘어남

    이벤트 발행 후 : 이벤트만 발행하면 이벤트 리스너가 처리하면 됩니다.

Q2. 별도의 트랜젝션으로 처리한 이유

@Transactional(propagation = Propagation.REQUIRES_NEW)와 관련

사용자 정보는 제거가 되었는데 후처리 작업에서 예외가 발생하여 롤백이 된다면 사용자 정보 제거 작업도 롤백이 되어야 할까요?

2-3번 작업은 정말로 후처리 작업입니다.

따라서 2-3번 작업에서 예외가 발생하여 롤백이 되었다고 해도 메인작업(사용자 정보 제거)이 롤백이 발생하지 않고 정상적으로 커밋되어야 한다고 생각했습니다.

따라서 별도의 트랜젝션으로 작업을 처리했습니다.

Q3. 비동기로 처리한 이유

@Async와 관련

유저가 후처리 작업까지 기다릴 필요가 없다고 생각했습니다.

즉, 메인 작업이 처리되었으면 유저는 정상적인 응답을 받게 됩니다.

Q4. 만약 메인작업에서 롤백이 발생한다면? 비동기로 처리했기 때문에 메인작업이 정상적으로 처리되지 않았음에도 후처리 작업을 실행하는 거 아닌가요?

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)와 관련

예외가 발생할 경우를 고려하여 트랜젝션이 commit된 이후에 후처리 작업을 실행할 수 있게 처리합니다.

아래 문서를 참고해주세요

Spring Event 사용하기

2~4번과 관련있는 코드입니다.(DeletedMemberEventListener.class)

2~4번과 관련있는 코드입니다.(DeletedMemberEventListener.class)

0개의 댓글