본문 바로가기
멘토링/NEXTSTEP ATDD

테스트와 인증(Auth)의 만남

by Alex_beom 2024. 7. 22.
이 글은 필자의 이전 블로그인 https://velog.io/@beomdrive/NEXTSTEP-ATDD-3에서 핵심 내용을 중심으로 요약한 후에, 내용을 추가 보완하였습니다.

 

겉핥기로만 알고 있던 "인증",  제대로 알아보다

이전 솔루션 회사에서는 별도의 Auth(인증) 서버가 존재하는 형태로 아키텍처가 구성되어 있었다.

3개의 애플리케이션 모듈이 각각 repository 단위로 분리되어 있는 Multi-Repo로 구성되어 있고, 모바일 애플리케이션도 존재했기 때문에 공통으로 관리하기 위함이었다.

 

이때 토큰 방식으로 인증이 이루어졌는데, 기존 세션 방식을 토큰 방식으로 전환된 이유는 아래와 같았다.

  • 세션 방식으로 진행할 때는 각 서버 간의 세션 정합성을 맞춰줘야 한다.
  • 이미 구축되어 있는 Redis를 통해 세션 클러스터링을 진행하면 웹 애플리케이션 간의 정합성은 충분히 맞출 수 있다.
  • 하지만 새로 추가되는 모바일 애플리케이션에서는 세션 인증 방식을 사용하기에 불편한 점이 있다. (ex. 쿠키 관리 등)
  • 또한, 다른 업체에서 우리의 애플리케이션과 연동할 때 따로 인증 관리를 해주어야 했다. (OAuth 2.0)

이와 같은 여러 가지 이유 때문에 Atuh 서버를 따로 구축하여 인증 토큰 방식을 사용하였고, 토큰의 종류는 OAuth 2.0 프로토콜에서 주로 사용되는 JWT Token을 사용하였다.

나머지의 디테일한 내용들은 이전에 사내 세미나를 진행하면서 준비했었던 자료를 참고하면 좋을 것 같다.

 

마침 이번 NEXTSTEP ATDD 과정에서도 Github를 연동한 로그인 기능이 미션에 포함되어 있어서, 이번 기회에 잊고 있었던 개념들을 다시 잡고 가보려고 한다.

OAuth 2.0 + JWT, 그 속으로 글에서도 정리가 되어 있지만, 한번 더 간략하게 인증 방식과 인증을 이용한 테스트 코드 작성 방법을 정리해 보겠다.

 

OAuth 2.0 인증 Flow

  1. 사용자 인증 요청 : Client -> Server
  2. 외부 서비스(Github)의 로그인 페이지를 응답 : Server --- Redirect ---> Client
  3. Github 로그인 성공 시 Github로부터 권한 증서(code)를 받음 : Client -> Github -> Client
    • code를 통해 인증(Autentication) 가능
  4. Github Authorization Server에 code를 전달하면 Resource Server에 접근할 수 있는 Access Token 발급
    • 필자는 Github AccessToken을 그대로 반환하지 않고, 내 애플리케이션에서 사용하는 AccessToken을 별도로 생성하여 반환하였다.
  5. Access Token을 가지고 Resource Server(필자의 API 애플리케이션 서버)에 정보 요청
  6. Resource Server에서 받은 Access Token 검증(Authorization) 후 요청한 정보(자원) 반환

 

인증 방식

인증 방식은 주로 2가지로 나누어진다.

  • 세션 기반
  • 토큰 기반

 

세션 기반 인증 프로세스

  1. 사용자 로그인(인증)
  2. 회원 DB에서 사용자 확인
  3. 인증 성공 시 회원 정보를 세션에 저장 (세션 저장소)하고 Session ID를 발급
  4. Session ID를 클라이언트에 반환
  5. 다음 요청 때 Session ID를 쿠키에 포함시켜서 요청
  6. 쿠키에 있는 Session ID를 통해 세션 저장소에서 검증
  7. 회원 정보(세션)를 획득하면 요청한 데이터와 함께 반환

 

토큰 기반 인증 프로세스

  1. 사용자 로그인(인증)
  2. 회원 DB에서 사용자 확인
  3. 사용자를 식별할 수 있는 정보 일부를 담아 토큰 생성 (ex. JWT)
  4. 클라이언트에 반환, 토큰 저장
  5. 다음 요청 때마다 토큰을 헤더에 포함시켜 전송
  6. 서버는 토큰을 검증하고, 검증에 성공하면 요청한 데이터와 함께 응답

 

RestAssured(테스트 라이브러리) 인증 도구

form()

  • form 로그인 진행 후 받은 세션 ID를 쿠키에 담아서 다음 요청을 진행함
    • 내부적으로 API 호출 전, 우선적으로 설정한 로그인 path에 요청 후 반환 값(세션 ID)을 헤더에 담아줌
    • 반환 값(세션 ID)을 헤더에 담은 후, 실제 API 호출 진행
RestAssured
   .given()
   .auth().form(email, password, new FormAuthConfig("/login/session", "email", "password")
   .when().get("/members/me")

 

 

preemptive()

  • 기존 HTTP 인증 프레임워크는 인증 토큰이 없는 상태로 API 요청을 진행해서 401을 먼저 반환받은 후, 인증 요청 절차를 시작하는 흐름을 가짐
  • 요청 형식 앞에 preemptive()를 선언한다면, 앞에 401을 받는 불필요한 핑퐁 횟수를 줄일 수 있음 (바로 인증 요청)
RestAssured
   .given()
   .auth().preemptive().basic(email, password)
   .when().get("/members/me")

 

 

.auth().oauth2()   or   .header(”Authorization”, “Bearer “ + accessToken)

  • 인증 토큰(Access Token)을 담아서 보내는 요청 방식
// .auth().oauth2() 방식
RestAssured
        .given()
        .auth().oauth2(accessToken)
        .when().get("/members/me")

// .header() 방식
RestAssured
        .given()
        .header("Authorization", "Bearer " + accessToken)
        .when().get("/members/me")
return new RequestSpecBuilder()
                .setAccept(MediaType.APPLICATION_JSON_VALUE)
                .setContentType(MediaType.APPLICATION_JSON_VALUE)
                .addHeader(HttpHeaders.AUTHORIZATION, AUTH_TYPE_OAUTH + Access_Token))
                .build();
  • 필자는 RestAssured.given() 을 사용한다면, 주로 .auth().oauth2() 를 사용한다.

 

외부 의존성 테스트

Github 요청처럼 외부 환경에 의존하는 테스트는 크게 3가지로 진행할 수 있다.

  • 실제 외부 의존성을 직접 사용 (실제 Github 호출)
  • Stub으로 외부 의존성 대체
  • Fake로 외부 의존성 대체

 

Stub

  • Stub 객체를 사용하여 외부 환경 요청을 Stubbing → 상태 검증
  • 하지만 Mock 방식을 사용하면 Test Context 캐싱이 되지 않고, 프러덕션 코드에 의존적인 테스트 코드가 된다는 단점이 존재한다.

 

Fake

  • 실제 외부 환경 요청을 다른 곳으로 우회
  • 테스트용 properties(ex. @Profile("test"))를 이용해 테스트 서버에 요청을 보내도록 우회
  • Fake 객체를 DI 받도록 하여 Fixture 데이터를 반환받게 우회