1. OAuth2 인증 핸들러 구현
1.1 Success Handler
@Component
@RequiredArgsConstructor
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal();
try {
String email = oauth2User.getAttribute("email");
String name = oauth2User.getAttribute("name");
String temporaryToken = jwtTokenProvider.createAccessToken(
email,
null,
name,
MemberRole.ROLE_GUEST
);
String targetUrl = UriComponentsBuilder.fromUriString("/auth/complete-profile")
.queryParam("token", temporaryToken)
.build().toUriString();
log.debug("Redirecting to: {}", targetUrl);
getRedirectStrategy().sendRedirect(request, response, targetUrl);
} catch (Exception e) {
log.error("OAuth2 인증 성공 처리 중 오류 발생", e);
throw new OAuth2AuthenticationException("OAuth2 authentication failed");
}
}
}
1.2 Failure Handler
@Component
@Slf4j
public class OAuth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException {
log.error("OAuth2 Authentication failure", exception);
String errorMessage = URLEncoder.encode("소셜 로그인 실패: " +
exception.getLocalizedMessage(),
StandardCharsets.UTF_8);
String targetUrl = UriComponentsBuilder.fromUriString("/auth/login")
.queryParam("error", errorMessage)
.build().toUriString();
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}
2. OAuth2 서비스 인터페이스 설계
public interface OAuth2Service {
LoginResponseDTO processOAuth2UserInfo(OAuth2User oauth2User);
OAuth2UserInfo extractOAuth2UserInfo(OAuth2User oauth2User);
boolean isNewUser(String email);
LoginResponseDTO loginOAuth2User(OAuth2UserInfo userInfo);
LoginResponseDTO completeRegistration(AdditionalUserInfoDTO additionalInfo);
}
3. OAuth2 서비스 구현
@Slf4j
@Service
@RequiredArgsConstructor
public class OAuth2ServiceImpl implements OAuth2Service {
private final MemberMapper memberMapper;
private final JwtTokenProvider jwtTokenProvider;
@Override
@Transactional
public LoginResponseDTO processOAuth2UserInfo(OAuth2User oauth2User) {
OAuth2UserInfo userInfo = extractOAuth2UserInfo(oauth2User);
try {
MemberDTO existingMember = memberMapper.findByEmail(userInfo.getEmail());
if (existingMember != null) {
return loginOAuth2User(userInfo);
} else {
MemberDTO temporaryMember = createTemporaryMember(userInfo);
memberMapper.insertMember(temporaryMember);
return LoginResponseDTO.builder()
.memberNo(temporaryMember.getMemberNo())
.id(temporaryMember.getEmail())
.name(temporaryMember.getName())
.role(temporaryMember.getRole())
.isNewUser(true)
.build();
}
} catch (Exception e) {
log.error("OAuth2 사용자 처리 중 오류 발생", e);
throw new OAuth2AuthenticationException("Failed to process OAuth2 user");
}
}
@Override
@Transactional
public LoginResponseDTO completeRegistration(AdditionalUserInfoDTO additionalInfo) {
MemberDTO member = memberMapper.findByEmail(additionalInfo.getEmail());
if (member == null) {
throw new RuntimeException("회원 정보를 찾을 수 없습니다.");
}
try {
MemberDTO updateDto = MemberDTO.builder()
.memberNo(member.getMemberNo())
.id(additionalInfo.getId())
.name(member.getName())
.email(member.getEmail())
.phone(additionalInfo.getPhone())
.address(additionalInfo.getAddress())
.birthday(additionalInfo.getBirthday())
.gender(additionalInfo.getGender())
.photoUrl(additionalInfo.getPhotoUrl())
.role(MemberRole.ROLE_USER)
.build();
memberMapper.updateMemberProfile(updateDto);
memberMapper.insertAgreements(
member.getMemberNo(),
additionalInfo.isTermsAgreed(),
additionalInfo.isPrivacyAgreed(),
additionalInfo.isMarketingAgreed()
);
if (additionalInfo.getDevFields() != null && !additionalInfo.getDevFields().isEmpty()) {
memberMapper.insertMemberDevFields(member.getMemberNo(), additionalInfo.getDevFields());
}
if (additionalInfo.getLanguages() != null && !additionalInfo.getLanguages().isEmpty()) {
memberMapper.insertMemberLanguages(member.getMemberNo(), additionalInfo.getLanguages());
}
MemberDTO finalMember = memberMapper.findByMemberNo(member.getMemberNo());
String accessToken = jwtTokenProvider.createAccessToken(
finalMember.getId(),
finalMember.getMemberNo(),
finalMember.getName(),
finalMember.getRole()
);
return LoginResponseDTO.builder()
.memberNo(finalMember.getMemberNo())
.id(finalMember.getId())
.name(finalMember.getName())
.role(finalMember.getRole())
.accessToken(accessToken)
.isNewUser(false)
.build();
} catch (Exception e) {
log.error("회원가입 완료 처리 중 오류 발생", e);
throw new RuntimeException("회원가입 완료 처리 중 오류가 발생했습니다.");
}
}
}
4. 주요 로직 설명
4.1 OAuth2 로그인 처리 흐름
- Google 로그인 시도
- OAuth2 인증 성공
- Success Handler에서 임시 토큰 생성
- 사용자 존재 여부 확인
- 기존 회원: 로그인 처리
- 신규 회원: 추가 정보 입력 페이지로 이동
4.2 추가 정보 입력 처리
- 임시 토큰으로 사용자 식별
- 기본 정보 업데이트
- 약관 동의 정보 저장
- 개발자 정보 저장
- 최종 토큰 발급
4.3 보안 고려사항
- 토큰의 권한 구분 (GUEST/USER)
- 트랜잭션 관리
- 예외 처리와 로깅