Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization header를 사용하는 인증 클래스 생성 #954

Merged
merged 25 commits into from
Dec 18, 2024

Conversation

zangsu
Copy link
Contributor

@zangsu zangsu commented Dec 6, 2024

⚡️ 관련 이슈

#908

file changed가 많이 잡혀서 코드 리뷰를 하기 용이하도록 작업을 분리하였습니다.
다음 작업때 해당 클래스를 사용하도록 코드가 변경될 예정입니다.

📍주요 변경 사항

  • AuthorizationHeaderCredentialManager 생성
    • Authorization 헤더를 사용하는 CredentialManager를 생성하였습니다.
  • Credential 클래스 생성
    • 인증 타입, 값을 가지는 래퍼 클래스 Credential을 생성하였습니다.

🎸기타

기존의 코드에서는 CredentialProviderCredentialManager가 개별적으로 동작하고 있었습니다.
ex) service에서 CredentialProviderString credential을 생성하고, controller에서 CredentialManager가 응답에 credential 설정

다만, CredentialManagerCredentialProvider는 어느 정도 함께 동작해야 하는 문제가 있었습니다. issue comment
이에 CredentialProvider의 외부 사용을 제거하고 CredentialManager에서 CredentialProvider의 의존성을 가지도록 변경하였습니다.

🍗 PR 첫 리뷰 마감 기한

12/11 00:00

@zangsu zangsu added refactor 요구사항이 바뀌지 않은 변경사항 BE 백엔드 labels Dec 6, 2024
@zangsu zangsu self-assigned this Dec 6, 2024
@zangsu
Copy link
Contributor Author

zangsu commented Dec 6, 2024

@jminkkk
마지막 커밋에서 기존의 HeaderCredentialCredential로 조금 더 일반화 해봤습니다.
이 부분은 몰리도 한번 확인해 주시고 편하게 의견 남겨주세욥...!

Copy link
Contributor

@zeus6768 zeus6768 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트가 아주 꼼꼼하네요~~

면접으로 바쁜 와중에 고생했어요!!

나름대로 꼼꼼히 보려고 노력했습니다 🔥


boolean hasCredential(HttpServletRequest httpServletRequest);

void setCredential(HttpServletResponse httpServletResponse, String credential);
void setCredential(HttpServletResponse httpServletResponse, Member member);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpServletResponse와 Member가 맞닿아있어서 불안하네요.

우리가 지금까지 DTO를 사용해 도메인과 웹을 격리했는데, 이렇게 되면 일관성이 깨지는 것 같아요.

CredentialManager와 CredentialProvider는 우리의 계층 구조에서 어디에 위치한다고 생각하나요?

저는 presentation 또는 web이라고 일컫는 계층에 있다고 생각하는데, 얘기를 나눠보고 싶네요.

자바 기본 타입을 사용하거나, DTO를 만들어서 데이터를 주고 받는 게 어떨까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CredentialManager는 단순히 인증 정보를 응답에 설정해 주는 책임만 가지도록 개선하였습니다.
이 과정에서 다시 CredentialManagerCredentialProvider 사이의 의존성이 제거되었으며 두 객체 모두 도메인 객체인 Member와 의존성을 가지지 않도록 LoginMember라는 dto를 추가하였습니다.

Copy link
Contributor

@jminkkk jminkkk Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 논의가 끝난 후인 것 같은데 다시 이야기해서 미안해요.
저는 반대로 생각해요 🥲
저는 여기서 DTO를 사용하면 일관성이 깨지는 것 같아요. 이 부분이 DTO로 변경되면 우리 이전 논의를 통해 결정했던 AuthArgumentResolver에서 반환하는 것도 Member가 되면 안된다고 생각해요.

제우스 의견처럼 다른 부분에서는 view에 영향을 최소화하기 위해 DTO를 사용하였지만 이 부분의 역할은 view에서 사용하기 위함은 아니라고 생각해요. Http 등 특정 웹 계층 관련 로직이 Member에 영향을 주면 안되지만 여기서는 그 �목적이 반대를 위함이기도 하구요.
오히려 DTO 관리 리소스가 드는 것 같은데 흠 다른 분들은 어떻게 생각하시나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

너무 어려운 주제군요 ㅠㅠ 고민을 더 해보느라 시간이 걸렸습니다...

결국 도메인과 web 계층을 얼마나 완벽하게 격리시키느냐의 문제인데요.
제가 느끼기에는 Member 도메인을 웹 계층과 완벽하게 분리하는 것은 이상적이지만 그만큼 또 어렵다고 느껴집니다. 몰리 말처럼 지금도 controller의 파라미터로 Member를 받고 있기도 하고요.

또한, 지금의 로그인이 두 가지 역할을 가지는 것도 어려움을 야기하는 또 다른 문제점인데요.

  1. 인증 정보를 응답에 포함한다. (Cookie / Authorization 헤더 설정)
  2. 로그인 정보를 바디에 포함한다. (LoginResponse를 반환)

이 때문에 authService.login() 메서드가 Member, 또는 해당 정보를 갖는 dto를 반환해야 하는 상황이에요.


이 문제에 대한 개인적인 선호는 도메인이 controller 영역까지 노출되거나 HttpServletResponse가 Service 영역까지 침투하는 것도 허용 가능한 범위에요.
특히 지금같은 경우 로그인이라는 기능이 "인증 정보 유지를 위한 설정"의 책임까지 가지고 있기 때문에 authService.login() 메서드의 파라미터로 response를 넘겨주는 것이 가장 마음이 편하기도 하고요.

다만, 이 부분에 대해선 각자 느끼는 기준, 허용치 등이 다를 것 같아서 의견을 더 주시면 감사하겠습니다.

Copy link
Contributor

@kyum-q kyum-q left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

짱수 고생했어요! 전반적으로 깔끔한 것 같네요 ! 리뷰를 뭘 할까 열심히 보는데 제우스가 이미 남긴 리뷰 외에 남길 리뷰가 없네요 ..!
제우스 리뷰 중에 동의하는 부분이 많아서 일단 코멘트로 리뷰 남길게요~ 리뷰 반영되고 바로 어프루브 하겠습니다~

Copy link
Contributor

@jminkkk jminkkk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

대부분 페어로 진행해서 따로 코멘트 남길 부분은 없네요!!
수고했어요 짱수 👍👍👍

Copy link
Contributor

@HoeSeong123 HoeSeong123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

으악 리뷰 마감기한이 어제까지였네요...
늦어서 죄송합니다ㅠㅠ
간단하게 확인해봤는데 켬미 말대로 제우스 코멘트 이외에 리뷰할만한 부분이 크게 없는 것 같네요.
너무 고생하셨습니다👍

Copy link
Contributor

@zeus6768 zeus6768 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생많았어요 짱수~!

NPE 위험이 있어 코멘트 하나만 더 추가했어요 😭

@@ -37,7 +36,7 @@ public Member resolveArgument(
if (!parameterAnnotation.required() && !credentialManager.hasCredential(request)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래와 같이 테스트코드 추가해서 확인해보니 parameterAnnotation에서 NPE가 발생할 수 있네요.

    @Nested
    @DisplayName("파라미터 반환 테스트")
    class ResolveArgument {

        static class ResolveTestController {
            public void notRequiredMethod(@AuthenticationPrinciple(required = false) Member member) ...

            public void requiredMethod(@AuthenticationPrinciple Member member) ...

            public void unsupportedMethod(Member member) {}
        }

        @Nested
        @DisplayName("required 값이 false 일 경우")
        class RequiredFalseTest {
            Method notRequiredMethod = ...

            Method unsupportedMethod = ReflectionSupport.findMethod(
                    ResolveTestController.class,
                    "unsupportedMethod",
                    Member.class).orElseThrow();

            @Test
            @DisplayName("NPE")
            void npeTest() {
                assertThatNullPointerException().isThrownBy(() -> resolveArgument(unsupportedMethod, nativeWebRequest));
            }

    ...

그래서 아래와 같이 개선 제안해요~~

Suggested change
if (!parameterAnnotation.required() && !credentialManager.hasCredential(request)) {
@Override
public Member resolveArgument(
@NonNull MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) {
AuthenticationPrinciple parameterAnnotation = parameter.getParameterAnnotation(AuthenticationPrinciple.class);
boolean supported = Objects.nonNull(parameterAnnotation);
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
if (supported && !parameterAnnotation.required() && !credentialManager.hasCredential(request)) {
return null;
}
Credential credential = credentialManager.getCredential(request);
return credentialProvider.extractMember(credential);
}

@@ -1,12 +1,12 @@
package codezap.auth.dto.response;

import codezap.member.domain.Member;
import codezap.auth.dto.LoginMember;

public record LoginResponse(
Long memberId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 null이 들어올 수 있는 경우가 있나요?

없다면 primitive type long으로 바꾸는게 좋을 것 같아요!

Comment on lines +35 to +36
credentialManager.setCredential(httpServletResponse, credential);
return ResponseEntity.ok(LoginResponse.from(loginMember));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제우스 리뷰에 코멘트 남긴 것에 이어서 의견 남길게요.
여기서도 LoginMember DTO를 다시 한번 감싼 것이 비용이라고 생각이 돼요 🥲

Copy link
Contributor

@HoeSeong123 HoeSeong123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다👍

Copy link
Contributor

@kyum-q kyum-q left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다~!!

@zangsu zangsu requested a review from zeus6768 December 17, 2024 11:39
@zeus6768 zeus6768 merged commit b274f8d into dev/be Dec 18, 2024
3 checks passed
@zeus6768 zeus6768 deleted the refactor/908-use-authorization-header branch December 18, 2024 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BE 백엔드 refactor 요구사항이 바뀌지 않은 변경사항
Projects
Status: Weekend Done
Development

Successfully merging this pull request may close these issues.

5 participants