12.4 OAuth2 + JWT 통합 로그인 구축 (Google, Kakao)
현대 웹/앱 서비스에서 자체 회원가입 외에 "Google로 로그인", "Kakao로 로그인" 같은 소셜 로그인은 필수 기능입니다. Spring Security의 OAuth2 Client를 활용하면 이 복잡한 인증 절차를 한 번에 캡슐화할 수 있습니다.
1. OAuth2 동작 흐름 (Authorization Code Grant)
- **클라이언트(브라우저)**가 "카카오 로그인" 버튼을 클릭해 카카오 인증 서버로 리다이렉트합니다.
- 유저가 카카오 아이디/비밀번호를 치고 권한 제공에 동의하면 카카오가 Authorization Code를 백엔드로 보내줍니다.
- 스프링 내부의
OAuth2LoginAuthenticationFilter가 이 코드를 가로채 카카오 서버 측과 통신하여 Access Token을 발급받습니다. - 발급받은 Access Token을 이용해 카카오에서 유저 프로필(이메일, 이름 등) 정보를 가져옵니다 (
DefaultOAuth2UserService동작). - 성공적으로 정보를 가져오면
OAuth2AuthenticationSuccessHandler가 동작합니다. 이곳에서 유저 정보를 DB에 저장(가입)시키고 우리 서버만의 자체 JWT 엑세스 토큰을 발급하여 브라우저로 응답합니다.
2. 의존성과 application.yml 설정
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
spring:
security:
oauth2:
client:
registration:
google:
client-id: "구글콘솔에서_발급받은_ID"
client-secret: "비밀번호"
scope: profile, email
kakao:
client-id: "카카오_REST_API키"
client-secret: "비밀"
client-authentication-method: POST
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
client-name: Kakao
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
3. CustomOAuth2UserService 작성
가져온 유저 정보를 가공하여 우리 DB 구조의 Entity와 매핑시키는 로직입니다.
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// 부모 클래스가 카카오/구글에서 통용 프로필 정보를 가져오는 행위 시작
OAuth2User oAuth2User = super.loadUser(userRequest);
// 어떤 플랫폼인지 확인 (google, kakao 등)
String registrationId = userRequest.getClientRegistration().getRegistrationId();
// 유저 정보 파싱 (플랫폼마다 JSON 응답 구조가 다르기 때문에 Factory 패턴 권장)
OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(registrationId, oAuth2User.getAttributes());
// DB에 해당 플랫폼 아이디가 있는지 조회하고, 없으면 신규 가입(save) 처리
User user = userRepository.findByEmail(userInfo.getEmail())
.orElseGet(() -> userRepository.save(User.createSocialUser(userInfo)));
// 시큐리티 세션에 담길 인증 객체 리턴 (PrincipalDetails 구현체)
return new PrincipalDetails(user, oAuth2User.getAttributes());
}
}
팁
이후 SuccessHandler를 만들어서 강제로 리다이렉션(response.sendRedirect(...)) 시킬 때 URL의 파라미터나 헤더 공간에 우리가 생성한 JWT를 심어서 프론트엔드 측으로 전달해주는 것이 OAuth2 통합 아키텍처의 핵심입니다.