티스토리 뷰

OAuth2 로그인을 하더라도 애플리케이션에서 회원 관리를 직접 하고 있다면

CustomOAuth2UserService를 만들어 우리의 DB에 OAuth2 로그인으로 받아온 회원 정보를 넣어줘야 한다

그렇다면 그 설정은 어찌하는가?

흔히 사용하는 SecurityConfig에서 http를 인자로 받는 메서드에서 설정을 잡아준다

수많은 튜토리얼들이 있으니 참고해보시고 최신 버전의 스프링부트 스타터들을 사용한다면 여기까지 설정했을 때

구글 로그인은 customOAuth2UserService를 타지 않는다

 

 

 

왜 내가 만든 커스텀 서비스를 안 타는지 이해 하려면 OAuth2, OIDC (open id connect) 두 방식의 차이를 이해해야 한다

OAuth2는 인가에 초점을 맞췄고 다양한 상황에 쓰일 수 있도록 만들어졌고 OIDC는 통합 인증에 초점을 맞춰놨다

깊게 들어가면 목적 자체가 다르지만 쉽게 생각해서 OIDC가 OAuth2를 확장해 만들었다고 보면 된다

자세한 설명은 아래 글을 참고하자

 

The Complete Guide to OAuth 2.0 and OpenID Connect Protocols

Find out how the most widely-used protocols for authentication and authorization really work

betterprogramming.pub

 

 

아래 사진은 Spring Security가 제공하는 CommonOAuth2Provider다

OAuth2 로그인에 사용되는 여러 정보들이 있는데 저 중 builder.scope()를 유심히 보면 openid가 있다

 

 

scope은 사용자의 정보를 가져올 범위를 지정하는데 openid란 무엇일까?

scope에 openid를 요청하면 일반적인 OAuth2가 아닌 OIDC 방식으로 동작하게 된다

그렇기 때문에 맨 위 사진 configure(http)에서 등록한 커스텀 서비스를 타지 않는 것이다

이를 쉽게 해결할 방법은 세 가지가 있다 (단 내가 모르는 것일 뿐 이보다 쉽게 해결할 방법이 있을 수 있다)

 

1. 확장성을 고려하지 않고 오직 구글 로그인만 지원하는 경우

CustomOAuth2Service 대신 CustomOidcService를 만들고 여기서 처리해버리면 된다

얘는 DefaultOAuth2UserService 대신 OidcUserService를 상속받아 만들면 된다

나는 OAuth2, OIDC 방식을 둘 다 써서 중복되는 코드를 없애고자 OAuth2Service 인터페이스를 만들었고

default method로 같은 로직을 사용하며 인자만 oidcUserRequest, oauthUserRequest로 따로 넘겨 처리한다

.and()
  .userInfoEndpoint()
  .oidcService(customOidcUserService)
## SecurityConfig configure(http) 설정 ##

@Service
@RequiredArgsConstructor
public class CustomOidcUserService extends OidcUserService implements OAuth2Service {

  private final OAuth2MemberService oAuth2MemberService;

  @Override
  @Transactional
  public OidcUser loadUser(OidcUserRequest oidcUserRequest) throws OAuth2AuthenticationException {
    OidcUser oidcUser = super.loadUser(oidcUserRequest);
    return getUserPrincipal(oidcUserRequest, oAuth2MemberService, oidcUser);
  }
}

 

2. 확장성을 고려하지만 OAuth2, OIDC Service 따로 만들고 싶지 않은 경우

CommonOAuth2Provider를 바꿔주면 된다

scope에 openid가 들어가는 것이 문제니까 CustomOAuth2Provider를 만들고 scope에 openid만 빼주도록 한다

구글은 OAuth2도 지원하므로 openid 빼버리면 customOAuth2UserService를 탈 수 있다

이렇게 하는 방식의 장점은 OAuth2 로그인 하나로 퉁칠 수 있게 되는 것이다

또한 Security가 구현해놓은 InMemoryClientRegistrationRepository에 의해 알아서 처리되는 과정에서

직접 만든 CustomOAuth2Provider로 등록하는 추가 과정이 필요하다

연습할 땐 이렇게 하는 것도 나쁘지 않지만 OIDC가 괜히 나온 게 아닐 테니 그다지 추천할 만한 방법은 아니다

나는 Naver, Kakao도 추가했기 때문에 Provider를 커스텀으로 만들어서 yml에서 관리할 필요 없이

CommonOAuth2Provider의 형태로 똑같이 등록해놨다

 

쉽다, 복붙하고 openid만 빼버린다

 

3. 확장성도 고려하고 OAuth2, OIDC 정식 지원하고 싶은 경우

가장 추천하는 방법이다, CustomOAuth2Service & CustomOidcService 둘 다 만들자

둘 다 지원한다고 해서 특별히 더 어려울 것도 없다

다만 로직 흐름은 비슷하니까 인터페이스 하나 따로 만들어서 둘을 묶어주기만 하자

 

자바 8부터 인터페이스에 디폴트 메서드가 가능하니 로직 자체를 인터페이스에서 구현하고

구현 클래스에서는 디폴트 메서드를 사용하되 인자만 각자의 것으로 넘겨주면 된다

DefaultOAuth2UserService, OidcUserService는 왜 상속할까?

얘네는 기본 설정으로 아무것도 설정하지 않을 때 얘네에 의해서 처리된다

따라서 super.loadUser()를 호출해 구현되어 있는 로직을 한번 타게 하고 넘기면 더 편하다

기본적인 예외 처리나 검증이 되어 있기 때문이다

 

DefaultOAuth2Service의 loadUser()

 

앞에서 언급했던 클래스들의 흐름을 잡을 수 있도록 짧게 코드를 첨부한다

.and()
  .userInfoEndpoint()
  .userService(customOAuth2UserService)
  .oidcUserService(customOidcUserService)
## SecurityConfig configure(http) 설정 ##

public interface OAuth2Service {

  default UserPrincipal getUserPrincipal(OAuth2UserRequest request, OAuth2MemberService oAuth2MemberService, OAuth2User user) {
    String registrationId = request.getClientRegistration().getRegistrationId();

    OAuth2Attributes attributes = OAuth2Attributes.of(registrationId, user.getAttributes());
    Member member = oAuth2MemberService.registerOrEdit(registrationId, attributes);

    return UserPrincipal.of(member, attributes.getAttributes());
  }
}

@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService implements OAuth2Service {

  private final OAuth2MemberService oAuth2MemberService;

  @Override
  @Transactional
  public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
    OAuth2User oAuth2User = super.loadUser(oAuth2UserRequest);
    return getUserPrincipal(oAuth2UserRequest, oAuth2MemberService, oAuth2User);
  }
}

@Service
@RequiredArgsConstructor
public class CustomOidcUserService extends OidcUserService implements OAuth2Service {

  private final OAuth2MemberService oAuth2MemberService;

  @Override
  @Transactional
  public OidcUser loadUser(OidcUserRequest oidcUserRequest) throws OAuth2AuthenticationException {
    OidcUser oidcUser = super.loadUser(oidcUserRequest);
    return getUserPrincipal(oidcUserRequest, oAuth2MemberService, oidcUser);
  }
}

 

댓글
링크
글 보관함
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Total
Today
Yesterday