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

[#8] 카카오 로그인 OIDC -> 자체 JWT 발급 방식 변경 #11

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

hynxp
Copy link
Collaborator

@hynxp hynxp commented Jan 14, 2025

#️⃣ Issue

🧑🏻‍🏫 메인 리뷰어 지정

📝 요약

기존에 세션 대신 idToken을 사용하려 했으나 자체 토큰 발급으로 변경

🛠 작업 내용

  1. 카카오에서 제공하는 idToken부분 관련 로직을 제거하고 자체적으로 accessToken&refreshToken을 발급하는 로직을 추가했습니다.
  2. 카카오 api를 추가로 사용하지 않을 것 같아 refreshToken도 자체적으로 발급하고 내부에서 관리함으로써 카카오 서버는 로그인 시에만 통신합니다.
  3. refreshToken으로 토큰들을 갱신하는 코드를 추가했습니다. 갱신된 refresh_token은 변경 감지로 저장합니다.

References

참고 블로그
참고 repo

🤔 리뷰 시 참고 사항

  1. 전체적으로 헷갈리는 부분이 많아 1차적으로 리뷰 받고자 PR 올립니다..!
  2. MemberService.save()메서드의 동작과 네이밍이 애매한 것 같기도 합니다..(save인데 기존회원이면 반환?)
  3. AuthServiceMemberService의 역할 분배가 잘 된건지 모르겠습니다. (테스트할 때 애매했어요,,)
  4. 지금 AuthContoller의 메서드들은 단순히 AuthService의 메서드를 호출하는 로직밖에 없는데 컨트롤러와 서비스에서 테스트를 어떻게 다르게 하는지 모르겠어요
  5. KakaoOAuthClient도 테스트 코드 작성하나요??

✅ 체크리스트

  • PR 제목을 명령형으로 작성했습니다.
  • PR을 연관되는 github issue에 연결했습니다.
  • 리뷰 리퀘스트 전에 셀프 리뷰를 진행했습니다
  • 변경사항에 대한 테스트코드를 추가했습니다. 또는, 테스트 코드가 필요없는 이유가 있습니다.

@hynxp hynxp added the feat New feature or request label Jan 14, 2025
@hynxp hynxp requested a review from blue000927 January 14, 2025 18:01
@hynxp hynxp self-assigned this Jan 14, 2025
Copy link

@blue000927 blue000927 left a comment

Choose a reason for hiding this comment

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

[PR 질문에 대한 답변]
1 ~ 3: 코멘트 달아두었습니다.

  1. 지금 AuthContoller의 메서드들은 단순히 AuthService의 메서드를 호출하는 로직밖에 없는데 컨트롤러와 서비스에서 테스트를 어떻게 다르게 하는지 모르겠어요

Controller와 Service가 하는 역할의 차이는 무엇일까요?
ControllerTest는 어떤 것을 검증하기 위한 것인지 조사해 보시고 저에게 답변 부탁드립니다.

  1. KakaoOAuthClient도 테스트 코드 작성하나요??
    테스트 코드가 필요할지 한 번 판단해보시고, 필요가 없다면 그 이유를 반대로 필요가 있어도 그 이유를 저에게 설명해 주세요.

String refreshToken = jwtTokenProvider.generateRefreshToken(member.getId());
memberService.updateRefreshToken(member.getId(), refreshToken);

AuthTokens authTokens = new AuthTokens(accessToken, jwtTokenProvider.getAccessTokenExpireTime(), refreshToken, jwtTokenProvider.getRefreshTokenExpireTime());

Choose a reason for hiding this comment

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

클라이언트 입장에서는 액세스 토큰 유효 시간이 몇분, 몇시간인지 알면 좋겠지만 그것보다는 "언제 토큰이 만료되는지 구체적인 시각"을 알고 싶어하지 않을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

만료일시를 반환하도록 변경했습니다~!

}
}

public String getMemberIdFromToken(String token) {

Choose a reason for hiding this comment

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

jwt 토큰의 payload가 항상 memberId는 아닐텐데 좀더 범용적인 메서드명으로 지어보면 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

getSubjectFromToken로 변경해보았습니다.

Comment on lines 62 to 63
} catch (Exception e) {
throw InvalidTokenException.EXCEPTION;

Choose a reason for hiding this comment

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

이렇게 되면 모든 예외가 InvalidTokenException으로 처리될텐데, 구체적으로 어떤 에러인지 스택 트레이스를 추적할 수 없을 것 같아요..! (나중에 로깅하신다 생각했을 때 어떤 문제가 있을지 고민해보세요.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

구체적인 예외를 로깅에서 확인할 수 있도록 수정했습니다.

Comment on lines 21 to 22
private static final long ACCESS_TOKEN_EXPIRE_TIME = 1_000L * 60 * 60;
private static final long REFRESH_TOKEN_EXPIRE_TIME = 1_000L * 60 * 60 * 24 * 14;

Choose a reason for hiding this comment

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

보통 설정 파일로 빼는 편이에요.
해커가 유효 시간을 우리 소스 코드를 보고 악용할 여지가 있을 거예요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 설정 파일로 분리했습니다.

@@ -0,0 +1,4 @@
package com.hyun.udong.auth.presentation.dto;

public record AuthTokens(String accessToken, long accessTokenAge, String refreshToken, long refreshTokenAge) {

Choose a reason for hiding this comment

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

age보다는 만료 시각을 주면 좋겠습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

만료일시를 반환하도록 변경했습니다~!

}

return MemberResponse.from(memberRepository.save(member));
public Member save(Member member) {

Choose a reason for hiding this comment

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

save인데 없으면 기존 회원을 반환하지말고 에러를 던지면 되지 않을까요?
있으면 update 없으면 create 이 과정을 같이 하고 싶으면 upsert 표현을 많이 씁니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

AuthService.kakaoLogin()에서 기존회원일 시 member의 id를 받아와야 해서요..! (토큰 발급 시 필요함)

Member member = memberService.findBySocialIdAndSocialType(profile.getId(), SocialType.KAKAO)
                .orElseGet(() -> memberService.save(profile.toMember()));

로 바꿔봤는데 괜찮을까요?

Comment on lines 28 to 31
public Member updateRefreshToken(Long id, String refreshToken) {
Member member = findById(id);
member.updateRefreshToken(refreshToken);
return member;

Choose a reason for hiding this comment

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

아마 이런 코드때문에 authService, memberService의 경계를 모호하게 느끼시는 것 같습니다.
authService가 memberRepository를 의존하면 어떨까요?

꼭 AService는 무조건 ARepository만 의존해야한다는 법칙은 없습니다 ㅎㅎ
오히려 MemberService는 refreshToken 관련 정보는 모르는 것이 더 자연스럽다고 생각해요.

Copy link
Collaborator Author

@hynxp hynxp Jan 15, 2025

Choose a reason for hiding this comment

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

와 그렇네요..! authService로 로직 이동했습니다!

수정한 코드는 jpa의 변경감지를 포기하게되는데 정상인거겠죠...?

@hynxp
Copy link
Collaborator Author

hynxp commented Jan 15, 2025

Controller와 Service가 하는 역할의 차이는 무엇일까요? ControllerTest는 어떤 것을 검증하기 위한 것인지 조사해 보시고 저에게 답변 부탁드립니다.

Controller
클라이언트의 요청을 가장 먼저 받는 계층입니다.
단위 테스트로는 MockMvc의 perform() 메서드를 사용하여 HTTP 요청을 만들어 테스트해볼 수 있습니다.
통합 테스트(E2E)로는 사용자 관점에서 애플리케이션이 실제로 어떻게 동작하는지를 테스트합니다.
통합 테스트라 함은 클라이언트의 HTTP 요청을 시작으로, 데이터베이스 레이어까지 내려갔다가, 클라이언트에게 HTTP 응답을 내려주는 일련의 모든 과정을 테스트 하는 것을 말합니다.

Service
애플리케이션의 핵심 비즈니스 로직을 담고 있는 계층입니다.
Repository나 다른 Buisiness Layer(Service)의 모듈과의 잦은 데이터 교환을 통해서 비즈니스 로직을 구현하기 때문에 Repository, Service를 비롯한 다른 모듈들과와 함께 통합 테스트를 합니다.

질문

  1. service 레이어는 제가 하던대로 테스트하고 컨트롤러는 RestAssured나 MockMvc사용해서 HTTP요청부터 응답까지 테스트하는 게 맞을까요?!
  2. service 레이어에서 단위 테스트를 하지 않아도 되는 이유가 궁금합니다.


테스트 코드가 필요할지 한 번 판단해보시고, 필요가 없다면 그 이유를 반대로 필요가 있어도 그 이유를 저에게 설명해 주세요.

필요 없다고 생각한 이유는 카카오가 준 code와 accessToken이 유효해야 한다가 전제인데 외부 api 호출은 모킹으로 테스트하기 때문에 가짜 값으로 api 테스트하는 게 의미가 있나?라고 생각했기 때문입니다.

질문

  1. 찾아보기로는 카카오에서 받은 값으로 DTO가 잘 변환되는지만 테스트해도 "외부 API가 정상적으로 동작한다면 카카오 로그인은 정상 동작합니다."를 검증할 수 있다는 점에서 가치가 있다고 하는데 이 부분만 테스트하는 걸까요?
  2. 컨트롤러도 마찬가지로 단위 테스트를 하지 않아도 되는 이유가 궁금합니다.

@blue000927
Copy link

  1. Controller와 Service가 하는 역할의 차이는 무엇일까요? ControllerTest는 어떤 것을 검증하기 위한 것인지 조사해 보시고 저에게 답변 부탁드립니다.

넵! 답변 잘해주셨네요.
controller 테스트의 목적은 실제로 API 요청을 하여 전 레이어의 걸친 비즈니스 로직이 잘 동작하는지 검증하기 위함입니다.
service 테스트는 현재 통합 테스트만 해주셨는데, 단위 테스트도 mocking을 통해 구현해 주셔도 됩니다. 다만, 지금 단위 테스트까지 하시기에는 시간 관계상 빡셀 것 같아서 생략하면 좋겠습니당

  1. 테스트 코드가 필요할지 한 번 판단해보시고, 필요가 없다면 그 이유를 반대로 필요가 있어도 그 이유를 저에게 설명해 주세요.

넵. 맞아요.
DTO 변환 정도를 위해 테스트 해볼 수 있긴한데, 굳이 지금 단계에서 ClientTest를 했을 때 크게 들이시는 시간 대비 이점이 적습니다.
컨트롤러도 단위 테스트는 슬라이스 테스트라고하는데 지금 단계에서 단위 테스트를 하는 것보다는 통합 테스트를 익히시는 것이 적합해보여서 추천드린 것이고, 나중에 시간 될 때 해보셔도 됩니다~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

카카오 로그인 OIDC -> 자체 JWT 발급
2 participants