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

[임지영] 1단계 OAuth 2.0 Login, 리팩터링 & OAuth 2.0 Resource 연동 #12

Open
wants to merge 43 commits into
base: sazzeo
Choose a base branch
from

Conversation

sazzeo
Copy link

@sazzeo sazzeo commented Feb 22, 2025

리뷰어님, 안녕하세요. 해당 미션에서 고군분투 하느라 첫 리뷰요청이 늦어졌습니다...!

OAuth2 프로토콜에서 redirect 하는 부분과 access token을 가져오는 부분까지는 동일하지만 user 정보를 가져오는 부분은 상이하다고 생각해서 이 부분을 OAuth2UserDetailsService 인터페이스로 만들었습니다.

타입 캐스팅하는 부분이나 상수 하드코딩 같은 곳 에서 맘에 안드는 부분이 많은데.. 일단 구현이 우선이라고 생각해서 제출합니다..!

제가 commit 메세지 같은 게 정돈되지 않아서 ㅠㅠ 혹시 이 부분에 대해서 가이드 주시면 다음부터 적용하겠습니다.

잘부탁드립니다.

sazzeo and others added 30 commits February 18, 2025 23:10
Copy link

@jaeyeonling jaeyeonling left a comment

Choose a reason for hiding this comment

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

안녕하세요! 이번 미션을 함께하게 된 김재연입니다. 잘 부탁드립니다!
코멘트 남겨두었으니 확인 부탁드릴게요 ㅎㅎ
이번 미션 화이팅입니다!!

public void saveOauth2Member(Oauth2MemberSaveDto dto) {
var member = memberRepository.findByEmail(dto.email());
if (member.isPresent()) {
return;

Choose a reason for hiding this comment

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

member가 중복될 경우 무시하는 것이 의도한 구현이 맞을까요?

Copy link
Author

Choose a reason for hiding this comment

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

네! 이미 있는 경우에만 member에 저장하고 있는 경우에는 저장하지 않도록 했습니다

Comment on lines 19 to 26
private final RestTemplate restTemplate;
@Value("${app.oauth2.github.user-url}")
private String userUrl;

public GithubOAuth2UserDetailService(final RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

Choose a reason for hiding this comment

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

Suggested change
private final RestTemplate restTemplate;
@Value("${app.oauth2.github.user-url}")
private String userUrl;
public GithubOAuth2UserDetailService(final RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
private final RestTemplate restTemplate;
private final String userUrl;
public GithubOAuth2UserDetailService(
final RestTemplate restTemplate,
@Value("${app.oauth2.github.user-url}") final String userUrl
) {
this.restTemplate = restTemplate;
this.userUrl = userUrl;
}

통일성있게 userUrl도 final로 만들어보면 좋겠네요!

Copy link
Author

Choose a reason for hiding this comment

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

오 이 부분은 코틀린에서도 되는건데 자바에서도 된다는 생각을 못했네요! 감사합니다!!

import java.util.Set;

@Component
public class GithubOAuth2UserDetailService implements OAuth2UserDetailsService {

Choose a reason for hiding this comment

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

지금은 벤더사마다 OAuth2UserDetailsService의 구현체를 만드는 방식으로 구현해 주셨는데요!
각 코드마다 중복되는 부분이 많이 발생하고 있는데, 공통으로 묶어 추상화해보면 좋을 것 같습니다 ㅎㅎ

Copy link
Author

Choose a reason for hiding this comment

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

이부분을 설계할 때 userInfo 를 받아오는 부분은 인가서버마다 스펙이 다르기 때문에
사용자(시큐리티를 사용하는 클라이언트)가 커스텀해야 되는 부분이라고 생각했습니다.
이번 미션이 핵심은 시큐리티 패키지고 app 패키지는 사용자마다 달라지는 부분이라 딱히 힘을 주지 않고 중복코드 가득하게 코드를 짰습니다...💦💦

여기서 중복 코드 추상화는 security 단에서 제공을 해줘야하는 부분인지? 아니면 사용자 관점에서 해야하는 추상화의 관점에서 얘기해 주신 부분인지 궁금합니다!

Choose a reason for hiding this comment

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

저는 인가 서버마다 사용자 정보를 받아오는 방식이 크게 다르지 않기 때문에 security 단에서 충분히 추상화할 수 있을 거라고 생각합니다!
어느 부분까지 추상화할 수 있을지 고민해보시고, 공통 로직은 DefaultOAuth2UserService처럼 추상화하고, 벤더사마다 차이가 필요한 부분은 확장 포인트로 설계해보는 것도 좋은 방향일 것 같은데 어떻게 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

이부분은 템플릿 메소드 패턴으로 개선해보았습니다
d799324

final List<OAuth2UserDetailsService> userDetailsServices
) {
this.oAuth2RegistrationRepository = oAuth2RegistrationRepository;
this.oAuth2AccessTokenRequestProvider = new OAuth2AccessTokenRequestProviderImpl();

Choose a reason for hiding this comment

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

해당 클래스만 생성자 주입이 아니게 설계하신 이유가 있을까요!?

Copy link
Author

Choose a reason for hiding this comment

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

이부분은 경우에는 거의 커스텀이 없을것 같은 역할들은 Default 객체로 생성하게 만들었습니다
스프링에서 보통 Default 로 생성자 안에서 만드는 객체들은 setter나 또 다른 생성자를 통해 객체를 교체할 수 있게 설계해서 만드는 걸 많이 봤는데요.
현재 경우에는 쓰이지 않는 메소드가 되어버려서 따로 추가하지 않았습니다. 프레임워크를 만들 때는 쓰지 않는 메소드라도 setter 를 추가해두는게 좋을까요?

Choose a reason for hiding this comment

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

맞습니다! 구현 방식에 따라 확장할 필요가 없는 부분이라면 내부에서 직접 생성하는 것도 좋은 방법이 될 수 있는데요.
다만, 그렇게 설계할 경우 인터페이스를 유지하는 이유와 충돌할 가능성이 있어서, 오히려 인터페이스를 제거하는 것이 코드의 의도가더 명확하게 드러날 것 같습니다 ㅎㅎ

Copy link
Author

Choose a reason for hiding this comment

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

제가 시큐리티를 쓸 때 종종
이걸 왜 커스텀 불가능하게 만들었지? 라는 생각이 들때가 있었는데요.
요즘에는 애초에 커스텀 하려고 하는 게 잘못된 생각인 건 아닌지 하는 의심을 합니다 ㅎㅎ

다음에는 스프링에 해당 부분이 인터페이스인지 아닌지 유심히 봐봐야겠네요..

filterChain.doFilter(servletRequest, servletResponse);
return;
}
StringBuffer url = httpServletRequest.getRequestURL();

Choose a reason for hiding this comment

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

Suggested change
StringBuffer url = httpServletRequest.getRequestURL();
String url = httpServletRequest.getRequestURI();

RequestURL로 가져올 경우 전체 URL을 반환하기 때문에 https://example.com/oauth2/authorization/google?state=some와 같은 쿼리 스트링이 있을 경우 올바르게 동작하지 않을 수 있겠네요.
getRequestURI로 변경해보면 어떨까요?

Comment on lines 15 to 16
uri: https://github.com/login/oauth/authorize
authorize-uri: https://github.com/login/oauth/authorize

Choose a reason for hiding this comment

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

uri와 authorize-uri를 중복해서 만든 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

이 부분은 파라미터 이름 수정하면서 지워지지 않은 부분이 있었던것 같습니다!
꼼꼼한 확인 감사합니다 ㅎㅎㅎ

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureWireMock(port = 8089)
class OAuth2AuthenticationFilterTest {

Choose a reason for hiding this comment

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

테스트 👍

import java.util.List;
import java.util.Set;

public class OAuth2AuthenticationFilter extends GenericFilterBean {

Choose a reason for hiding this comment

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

전체적으로 Spring Security의 구조와 유사하게 잘 구현해 주셨습니다. 확장성을 고려한 설계도 인상적이네요! 👍
다만, 코드의 확장성과 설정값 중 이번 미션에서 요구되지 않는 정보가 포함되면서 코드의 복잡도가 다소 높아진 것 같은데요!
구현 범위를 고려하여, 조금 더 간결한 형태로 정리해 보면 유지보수성이 더욱 향상될 것 같은데 어떻게 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

이 부분은 따로 디엠 드리겠습니다..!

}
StringBuffer url = httpServletRequest.getRequestURL();
var vendor = url.substring(url.lastIndexOf("/") + 1);
var registration = oAuth2RegistrationRepository.getRegistration(vendor);

Choose a reason for hiding this comment

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

현재 oAuth2RegistrationRepository를 통해 registration을 가져오는 방식이 조금 어색하게 느껴지는데요.
이와 같은 설계를 선택하신 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

OAuth2RegistrationRepository 는 단순 데이터 객체인 OAuth2Properties를 랩핑해서 여러가지 Registration 정보를 가져오게 해주는 중간 객체 역할로 설계했습니다.
시큐리티의 ClientRegistrationRepository 랑 유사한 역할로 설계했습니다.
이 부분은 만들다보니 다음 단계 미션으로 있는 것 같아서 적당히 만들었던 것 같습니다

Copy link
Author

Choose a reason for hiding this comment

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

혹시 어색하게 느껴지는 부분이 OAuth2RegistrationRepository 이 app 패키지에 있어서일까요? 이부분은 제가 auto configuration 으로 만들고 싶었는데 시간 부족으로 bean 주입이 필요해서 어쩔 수 없이 패키지 의존성 구조가 꼬이게 되었습니다.

Choose a reason for hiding this comment

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

DM으로 나눈 내용과 조금 연관되는 것 같아요! oAuth2RegistrationRepository의 역할이 단순히 properties를 포장한 형태로 구현되어 있어서 불필요한 레이어가 있는 느낌이 들었습니다!
조금 더 의도가 명확한 코드를 생각해본다면

  1. OAuth2RegistrationRepository를 제거하여 Properties 자체가 역할을 가지도록 한다.
  • 불필요한 레이어를 줄여 코드가 단순해진다.
  1. Properties의 역할을 OAuth2RegistrationRepository가 가지도록 한다.
  • OAuth2RegistrationRepository의 역할이 명확해진다.
  • Properties도 Properties 자체의 역할을 할 수 있다.
  1. OAuth2RegistrationRepository를 저장소의 확장 포인트로 설계한다. (인터페이스 제공)
  • 코드는 그대로 있더라도, 확장 포인트를 위해 Repository가 있다는 점에서 근거가 명확해진다.

와 같은 방식이 조금 더 코드에 의도를 담는 것 같은데 어떻게 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

OAuth2RegistrationRepository 를 인터페이스로 바꾸면서 ClientRegistrationRepository 로 이름을 변경했습니다

인터페이스로 바꾸고 구현체를 설계하니 제가 이전에 생각했던 의존성이 꼬이는 문제가 해결되더라구요.
사용하는 쪽에서 인터페이스를 바라보니
자연스럽게 Registration 속 requestMatcher를 사용하는 부분이 사라지면서 책임도 명확해지고 코드도 깔끔해진것 같습니다.

좋은 포인트를 짚어주셔서 감사합니다.

그리고 redirect url 을 공통으로 가져가면서 domain 부분이 필요하게 되었는데요.
domain 을 OAuth2Properties.java 에서 아래로 넘겨주는데 이건 어색하지 않은지가 궁금합니다.

감사합니다.

Copy link

@jaeyeonling jaeyeonling left a comment

Choose a reason for hiding this comment

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

질문주신 내용에 코멘트 남겨두었습니다 ㅎㅎ

import java.util.Set;

@Component
public class GithubOAuth2UserDetailService implements OAuth2UserDetailsService {

Choose a reason for hiding this comment

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

저는 인가 서버마다 사용자 정보를 받아오는 방식이 크게 다르지 않기 때문에 security 단에서 충분히 추상화할 수 있을 거라고 생각합니다!
어느 부분까지 추상화할 수 있을지 고민해보시고, 공통 로직은 DefaultOAuth2UserService처럼 추상화하고, 벤더사마다 차이가 필요한 부분은 확장 포인트로 설계해보는 것도 좋은 방향일 것 같은데 어떻게 생각하시나요?

final List<OAuth2UserDetailsService> userDetailsServices
) {
this.oAuth2RegistrationRepository = oAuth2RegistrationRepository;
this.oAuth2AccessTokenRequestProvider = new OAuth2AccessTokenRequestProviderImpl();

Choose a reason for hiding this comment

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

맞습니다! 구현 방식에 따라 확장할 필요가 없는 부분이라면 내부에서 직접 생성하는 것도 좋은 방법이 될 수 있는데요.
다만, 그렇게 설계할 경우 인터페이스를 유지하는 이유와 충돌할 가능성이 있어서, 오히려 인터페이스를 제거하는 것이 코드의 의도가더 명확하게 드러날 것 같습니다 ㅎㅎ

}
StringBuffer url = httpServletRequest.getRequestURL();
var vendor = url.substring(url.lastIndexOf("/") + 1);
var registration = oAuth2RegistrationRepository.getRegistration(vendor);

Choose a reason for hiding this comment

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

DM으로 나눈 내용과 조금 연관되는 것 같아요! oAuth2RegistrationRepository의 역할이 단순히 properties를 포장한 형태로 구현되어 있어서 불필요한 레이어가 있는 느낌이 들었습니다!
조금 더 의도가 명확한 코드를 생각해본다면

  1. OAuth2RegistrationRepository를 제거하여 Properties 자체가 역할을 가지도록 한다.
  • 불필요한 레이어를 줄여 코드가 단순해진다.
  1. Properties의 역할을 OAuth2RegistrationRepository가 가지도록 한다.
  • OAuth2RegistrationRepository의 역할이 명확해진다.
  • Properties도 Properties 자체의 역할을 할 수 있다.
  1. OAuth2RegistrationRepository를 저장소의 확장 포인트로 설계한다. (인터페이스 제공)
  • 코드는 그대로 있더라도, 확장 포인트를 위해 Repository가 있다는 점에서 근거가 명확해진다.

와 같은 방식이 조금 더 코드에 의도를 담는 것 같은데 어떻게 생각하시나요?

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

Successfully merging this pull request may close these issues.

2 participants