NEXTSTEP 4주차 - 테스트 인증, 그 속으로

gibeom·2023년 3월 8일
0

멘토링

목록 보기
13/15
post-thumbnail

뭉뚱그려 알고있던 "인증", 제대로 알아보다

전 직장을 다니고 있을 때 사내에서는 애플리케이션 모듈이 모두 분리되고, 모바일 어플도 추가돼서 Auth 서버가 만들어졌었다.
즉, 4개로 분리된 웹 애플리케이션과 모바일 애플리케이션이 공통된 인증이나 권한 관리가 필요하여 만들어진 것이다.

이 때 토큰 방식으로 인증을 진행하였는데, 이유는 아래와 같다.

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

이처럼 여러가지 이유를 통해 Auth 서버를 따로 구축하고, 인증 토큰 방식을 사용하였고 토큰의 종류는 OAuth 2.0 프로토콜에서 주로 사용되는 JWT Token을 사용하였다.
나머지의 디테일한 내용들은 이전에 사내 세미나를 진행하면서 준비했었던 자료를 참고하면 좋을 것 같다.


마침 ATDD 과정에서도 Github를 연동한 로그인 기능을 미션을 줘서 잊고 있었던 개념들을 다시 한번 일깨우는 경험이었다.
위의 글에서도 정리가 되어있지만, 한번 더 간략하게 인증 방식과 인증을 이용한 테스트 작성 방법을 정리해보겠다.

OAuth 2.0 인증 Flow

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

인증 방식

  • 세션 기반
  • 토큰 기반
  • OAuth 2.0 : 서드파티 로그인(인증) 때 많이 사용

세션 기반 인증 프로세스

  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를 쿠키에 담아서 다음 요청을 진행함

    • 실제 요청 전 먼저 해당 path에 요청 후 반환 값을 헤더에 담아줌
    		.auth().form(email, password, new FormAuthConfig("/login/session", "email", "password")
    		.when().get("/members/me")

preemptive()

  • 기존 HTTP 인증 프레임워크는 인증 토큰이 없는 상태로 요청해서 401을 먼저 반환 받은 후, 인증 요청 절차를 시작하는 Flow
  • preemptive()를 붙인다면 앞에 401 받는 불필요한 핑퐁 횟수를 줄여줄 수 있음 → 바로 인증 요청
    		.auth().preemptive().basic(email, password)
    		.when().get("/members/me")

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

  • 인증 토큰(AccessToken)을 담아서 보내는 요청 방식
      .auth().oauth2(accessToken)
      .when().get("/members/me")
      
      // or
      
      .header("Authorization", "Bearer " + 로그인_사용자.getAccessToken())
  • 만약 RequestSpecBuilder()를 통해서 인수 테스트의 세팅을 공통적으로 사용하고 싶을 때는 .addHheader()를 통해 세팅해주면 됨
  • 그냥 given()을 사용하는 경우에는 .auth().oauth2() 사용

추가 학습

외부 의존성 테스트

실제 Github 요청처럼 외부 환경에 의존하는 테스트는 크게 3가지로 진행

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

Stub

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

Fake

  • 실제 외부 환경 요청을 다른곳으로 우회
    • 테스트용 properties를 이용해 테스트 서버에 요청을 보내도록 우회
    • Fake 객체 Bean으로 동작하게 하여 Fixture 데이터를 반환받게 우회

@ElementCollection, @CollectionTable

값 타입의 라이프 사이클은 엔티티를 따라간다

  • 값 타입은 독립적인 라이프 사이클을 가질 수 없다
  • 따라서 별도로 persist()를 해주지 않아도 엔티티의 값이 변경되면 알아서 자동 반영된다
  • 영속성 전이 + 고아객체 기능을 기본적으로 가져간다

CollectionTable

  • name : 값 타입을 저장할 별도의 테이블 명
  • joinColumns : 참조할 fk 컬럼명 (보통 소속된 엔티티의 pk값)
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(
        name = "MEMBER_ROLE", // 값 타입 용 별도 테이블 명
        joinColumns = @JoinColumn(name = "id", referencedColumnName = "id") // id랑 fk
)
@Column(name = "role")
private List<String> roles;

생성되는 테이블 관계

profile
꾸준함의 가치를 향해 📈

0개의 댓글