OAuth2로 로그인 시 추가 정보 입력받기
기존 프로젝트에서는 OAuth2 로그인을 통해 소셜 제공자로부터 전달받은 정보만으로 로그인을 처리했다.
하지만 이번에는 로그인 이후 추가로 닉네임과 생년월일을 입력받아 완전한 회원 정보를 구성하고 싶었다.
이를 위해 User
엔티티에 nickname
필드(도메인 객체 Nickname
VO)와 birthDate
필드(LocalDate
타입)를 추가했다.
또한 현재는 Google OAuth2 로그인만 구현되어 있지만, 앞으로 Naver, Kakao 등 다양한 소셜 로그인을 지원할 계획이기 때문에 제공자 정보를 나타내는 provider
필드도 함께 추가했다.
SecurityConfig
를 살펴보면, 인증 성공 후 사용자 정보를 로드하는 부분은 CustomOAuth2UserService
, 로그인 성공 이후의 처리를 담당하는 부분은 OAuth2LoginSuccessHandler
로 나뉘어 있다.
이 구조에 맞춰 두 클래스를 역할에 맞게 리팩토링했다.
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. 로그인 성공 후 처리할 핸들러 등록
);
@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()
);
}
}
userRepository.findByEmail()
의 결과에 따라 processExistingUser
와 processNewUser
로 로직을 완전히 분리했다. 덕분에 각 상황에 맞는 처리가 가능해졌다.(processNewUser)