Skip to content

1-1단계 - OAuth 2.0 Login, 1-2단계 - 리팩터링 & OAuth 2.0 Resource 연동 리뷰 요청드립니다. #13

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

Merged
merged 15 commits into from
Feb 26, 2025

Conversation

jukekxm
Copy link

@jukekxm jukekxm commented Feb 23, 2025

  • Github 로그인을 할 때, user profile을 조회 시 이메일 정보가 안 내려옵니다.. (email이 null로 내려오네요)
    혹시 계정 문제일까요?
  • OAuth2 로그인을 요청할 때 적절한 OAUth2 Client로 프로퍼티 값을 불러오기 위해서, oAuth2ClientFactory 라는 Bean을 추가했습니다.
    더 좋은 방식이 있을 것 같은데 떠오르지 않네요..! 의견 부탁드립니다.
  • 패키지 간 양방향 참조를 방지하기 위해, OAuth2Property 클래스와 OAuth2Client 클래스를 따로 분리했는데, 더 나은 방식이 있는지 궁금합니다.

리뷰 부탁드립니다!

Copy link

@jinyoungchoi95 jinyoungchoi95 left a comment

Choose a reason for hiding this comment

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

안녕하세요 주현님 😄

OAuth2 미션을 함께하게 된 최진영입니다.

미션 잘 진행해주셨네요 👍 몇가지 코멘트 남겨두었으니 확인부탁드려요 😄

궁금하거나 고민이 되는 부분이 있으시다면 언제든 pr 코멘트 또는 dm으로 요청 부탁드립니다.
감사합니다 🙇‍♂️

Choose a reason for hiding this comment

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

Github 로그인을 할 때, user profile을 조회 시 이메일 정보가 안 내려옵니다.. (email이 null로 내려오네요) 혹시 계정 문제일까요?

nextauthjs/next-auth#374 라는 이슈가 있습니다.

@@ -2,4 +2,5 @@

public interface UserDetailsService {
UserDetails loadUserByUsername(String username);
UserDetails signUpUser(UserDetails userDetails);

Choose a reason for hiding this comment

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

실제 UserDetailsService는 해당 메소드를 가지고 있지 않아요. UserDetailsService는 오로지 username에 대한 실제 유저 검증용으로 고정되어 사용되기 때문에 가입한다거나 하는 일반적으로 저희가 아는 서비스의 역할과는 거리가 멉니다.

https://github.com/spring-projects/spring-security/blob/main/core/src/main/java/org/springframework/security/core/userdetails/UserDetailsService.java

@@ -0,0 +1,15 @@
oauth2:
google:
clientId: CLIENT_ID

Choose a reason for hiding this comment

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

clientId는 식별값이어서 굳이 막지 않아도 되지 않을까요?

this.github = github;
}

// public static class OAuth2Client {

Choose a reason for hiding this comment

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

주석은 제거해주시면 좋을 것 같아요

Comment on lines 13 to 14
private OAuth2Client google;
private OAuth2Client github;

Choose a reason for hiding this comment

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

변수로 각각 들고 있게 되는 경우, 인증방식(ex. 카카오 oauth)가 늘어나게 되는 경우 계속해서 여기 코드를 변경해야하는 문제가 있어요.

yml만으로 여러 인증방식을 추가하게 하기 위해서 Map<String, ?>으로 받아보는 방식이 있을 것 같네요. key는 "google", "github"와 같은 값으로 들어가구요.

Copy link
Author

Choose a reason for hiding this comment

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

Map으로만 받을 수 있도록 수정했습니다. 수정한 방식대로면 인증방식이 늘어날 때마다 oAuth2ClientRepository Bean에 인증방식이 추가되어야 하는데, 더 좋은 방법이 있을까요?

Choose a reason for hiding this comment

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

oAuth2Property.getClients()
        .entrySet()
        .stream()
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))

로 clients 자체를 활용하면 따로 하드코딩안해도 될 것 같아요 :)


import java.util.Map;

public class OAuth2ClientFactory {

Choose a reason for hiding this comment

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

Factory는 생성을 담당하는 객체이기 때문에 현재 객체는 요청정보에 맞는 ClientRegistration을 가져다주는 repository가 조금 더 적절한 네이밍이 되겠네요.

그래서 현재 객체의 역할은 사실 RequestMatcher로 복잡할 필요없이 registrationId에 맞는 ClientRegistration을 가져오는 역할로만 구성해주시면 됩니다.

https://github.com/spring-projects/spring-security/blob/main/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/InMemoryClientRegistrationRepository.java

Copy link
Author

Choose a reason for hiding this comment

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

RequestMatcher의 역할은 필터에서 수행하는 게 더 적절해보여서 수정했고, 네이밍도 수정했습니다!

}
};

UserDetails newUser = userDetailsService.signUpUser(newUserDetails);

Choose a reason for hiding this comment

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

위에서 말씀드렸듯 그래서 유저가 들어온 것에 대해서 회원가입을 시켜주는 것은 현재 spring security의 관심사는 아닙니다.

try {
userDetails = userDetailsService.loadUserByUsername(authentication.getPrincipal().toString());
return UsernamePasswordAuthenticationToken.authenticated(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
} catch (AuthenticationException e) {

Choose a reason for hiding this comment

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

인가 에러가 나면 유저를 던져주기보다는 401에러가 나야겠네요 :)

Copy link
Author

Choose a reason for hiding this comment

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

'새로운 멤버인 경우 회원 가입 후 세션에 로그인 정보를 저장한 뒤 "/"으로 리다이렉트'
위 요구사항을 구현하기 위해서 이렇게 구현했었습니다..!
예외 던지도록 수정했습니다!

Comment on lines 38 to 42
OAuth2Client oAuth2Client = oAuth2ClientFactory.getOAuth2Client(request);
if (oAuth2Client == null) {
filterChain.doFilter(request, response);
return;
}

Choose a reason for hiding this comment

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

oauth2 설정 가져온 값이 없는 경우에는 현재 서버가 해당 플랫폼을 지원하지 않기 때문에 에러가 나는 것이 맞습니다 :)

Comment on lines 50 to 53
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticated);
SecurityContextHolder.setContext(context);
securityContextRepository.saveContext(context, request, response);

Choose a reason for hiding this comment

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

oauth2에 대해서 사실 spring security의 역할은 여기서 끝이기 때문에 SecurityContext를 관리하는 것은 관심사가 아닐 수 있어요.

oauth2 플로우에서 중요한 것은 유저에게 oauth2로 가는 길을 열어주고, 유저가 oauth2를 통해서 가져온 토큰이 정상적인지 판단해주는 것까지가 그 역할이라고 봐주시면 좋을 것 같습니다.

Copy link

@jinyoungchoi95 jinyoungchoi95 left a comment

Choose a reason for hiding this comment

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

안녕하세요 주현님 😄

피드백 잘 반영해주셨네요 👍 다음단계 진행에 도움될만한 코멘트 몇가지 드렸습니다
다음 단계는 실제 구현체에 대한 내용을 반영하는 리팩터링인데요. 실제 spring security 구현체 코드는 많이 복잡하기 때문에 sample 코드를 먼저 보시고 전체적인 흐름을 파악해보시면 이해하는데 도움이 되실 것 같아요 😄

궁금하거나 고민이 되는 부분이 있으시다면 언제든 pr 코멘트 또는 dm으로 요청 부탁드립니다.
감사합니다 🙇‍♂️

public class OAuth2AuthenticationFilter extends OncePerRequestFilter {

private final RestTemplate restTemplate = new RestTemplate();
private static final RegexRequestMatcher OAUTH2_AUTHENTICATION_REQUEST_MATCHER = new RegexRequestMatcher(HttpMethod.GET, "/login/oauth2/code/.*");

Choose a reason for hiding this comment

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

사실 상수는 "/login/oauth2/code/.*"까지만 상수로 처리하고 RegexRequestMatcher는 주입받아 사용하는 형태로 구성되어요. 그리고 RequestMatcher로 추상화하여 가짐으로써 테스트하기 쉬운 구조로 가져가게됩니다 :)

Comment on lines +25 to +31
if (!OAUTH2_LOGIN_REQUEST_MATCHER.matches(request)) {
filterChain.doFilter(request, response);
return;
}

String clientRegistrationId = extractRegistrationId(request);
ClientRegistration clientRegistration = oAuth2ClientRepository.findByRegistrationId(clientRegistrationId);

Choose a reason for hiding this comment

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

다음 단계 진행하실때는 요런 로직들이 모두 OAuth2AuthorizationRequestResolver안에 들어간다고 봐주시면 됩니다 :)

Comment on lines +63 to +65
private static String extractRegistrationId(HttpServletRequest request) {
return request.getRequestURI().substring("/login/oauth2/code/".length());
}

Choose a reason for hiding this comment

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

아마 sample 코드에선 이렇게 나와있을건데요. 실제 구현체를 따라가보면 OAuth2AuthenticationFilter는 path에서 registrationId를 추출하지는 않아요.

요청 흐름을 살펴보면,

  1. RedirectFilter에서 registrationId 추출하여 request의 session에 저장 후 리다이랙
  2. 유저는 이 session을 가진채로 oauth 서버에 요청하여 받은 토큰을 AuthenticationFilter로 전달
  3. Authentication은 path에 있는 값을 추출할 필요없이 session에 있는 값을 꺼내옴

으로 구성되어있습니다.

@jinyoungchoi95 jinyoungchoi95 merged commit 1600e1c into next-step:jukekxm Feb 26, 2025
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