OAuth2로 로그인 시 추가 정보 입력받기

기존 프로젝트에서는 OAuth2 로그인을 통해 소셜 제공자로부터 전달받은 정보만으로 로그인을 처리했다.

하지만 이번에는 로그인 이후 추가로 닉네임과 생년월일을 입력받아 완전한 회원 정보를 구성하고 싶었다.

이를 위해 User 엔티티에 nickname 필드(도메인 객체 Nickname VO)와 birthDate 필드(LocalDate 타입)를 추가했다.

또한 현재는 Google OAuth2 로그인만 구현되어 있지만, 앞으로 Naver, Kakao 등 다양한 소셜 로그인을 지원할 계획이기 때문에 제공자 정보를 나타내는 provider 필드도 함께 추가했다.

SecurityConfig를 살펴보면, 인증 성공 후 사용자 정보를 로드하는 부분은 CustomOAuth2UserService, 로그인 성공 이후의 처리를 담당하는 부분은 OAuth2LoginSuccessHandler로 나뉘어 있다.

이 구조에 맞춰 두 클래스를 역할에 맞게 리팩토링했다.

SecurityConfig

    httpSecurity
                .csrf(csrf->csrf.disable())
                .headers(headers->headers.frameOptions().disable())
                .authorizeHttpRequests(auth->auth
                        .requestMatchers("/","/css/**","/images/**","/js/**","/h2-console/**","/NEW","/ARTIST","/REVIEW","/TALK","/CONCERT").permitAll()
                        .requestMatchers("/api/v1/**").hasRole(Role.USER.name()) //.name()은 "USER" 리턴 -> hasRole에는 "ROLE_"없이 넣지만 내부적으로 "ROLE_"이 붙어서 비교됨
                        .anyRequest().authenticated()
                )
                .logout(logout->logout.logoutSuccessUrl("/"))
                .oauth2Login(oauth2 -> oauth2
                        .userInfoEndpoint(userInfo->userInfo
                                .userService(customOAuth2UserService))    //1. 유저 정보 가져올 때 쓸 서비스 등록
                        .successHandler(oAuth2LoginSuccessHandler)        //2. 로그인 성공 후 처리할 핸들러 등록
                );

수정 후 CustomOAuth2UserService

@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final UserRepository userRepository;
    // 1. DefaultOAuth2UserService를 상수로 선언하여 재사용
    private final OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
    private final HttpSession httpSession;

    @Override
    @Transactional
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        // 위임(Delegation)을 통해 기본적인 사용자 정보 로드
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        // 현재 로그인 진행 중인 서비스를 구분 (e.g., "google", "naver", ...)
        String registrationId = userRequest.getClientRegistration().getRegistrationId();

        // OAuth2 로그인 진행 시 키가 되는 필드 값 (PK)
        String userNameAttributeName = userRequest.getClientRegistration()
                .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

        // 소셜 로그인 플랫폼별 속성을 DTO(OAuthAttributes)로 변환
        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());

        //이메일로 기존 사용자인지 신규 사용자인지 확인
        Optional<User> userOptional = userRepository.findByEmail(attributes.getEmail());

        if(userOptional.isPresent()){
           return processExistingUser(userOptional.get(), attributes);
        }else{
           return processNewUser(attributes);
        }
    }
    // 기존 사용자 처리
    public OAuth2User processExistingUser(User user, OAuthAttributes attributes){
        user.update(attributes.getName(),attributes.getPicture());//소셜 프로필 변경사항 업데이트

        httpSession.setAttribute("user",new SessionUser(user));

        // 인증된 사용자 정보를 Spring Security Context에 저장하기 위해 반환
        // 이 과정이 끝나면 SecurityContextHolder에 의해 세션에 사용자 정보가 저장된다.
        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey()
        );
    }

    //신규 사용자 처리
    public OAuth2User processNewUser(OAuthAttributes attributes){
        //2. 신규 사용자인 경우 (가회원 등록)
        // 세션에 소셜 로그인 정보 임시 등록
        httpSession.setAttribute("social_info",attributes);

        //가회원 권한 부여
        //singleton으로 권한이 하나뿐인 사용자 만듬(요소 하나만 들어있는 불변 Set)
        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(Role.GUEST.getKey())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey()
        );
    }
} 

기존 코드 문제점

바뀐 점

1. 신규/기존 사용자 로직 분리

2. GUEST 상태의 도입 (processNewUser)