목표
프론트에서 "구글 로그인"을 누르면
Spring 서버가 로그인 처리 → 사용자 정보 저장 → JWT 발급 → 클라이언트 전달
이 전체 흐름을 이해하는 게 목표야!
1. 전체 OAuth 로그인 흐름
[프론트]
사용자가 구글 로그인 버튼 클릭
↓
GET /oauth2/authorization/google
(Spring이 자동 제공하는 URL)
[Spring 서버]
→ Google 로그인 페이지로 리다이렉트
↓
→ 사용자가 로그인 후 동의
↓
Google → Spring으로 "code" 전송 (리디렉션 URL로)
[Spring 서버]
→ code를 access_token으로 교환
→ access_token으로 사용자 정보(email, name 등) 조회
→ OAuth2User 객체로 변환됨
2. 이후 흐름 (커스텀 처리)
[OAuth2User] (구글에서 받은 사용자 정보)
↓
OAuth2LoginSuccessHandler 실행
↓
- DB에 유저 저장 or 조회
- JWT 생성
- 프론트로 리다이렉트 + 토큰 전달
Spring이 자동으로 해주는 것들
Spring boot 6.x.x 버전 이상이면 디펜던시에 아래 코드를 추가하고, 기능을 직접 구현할 필요 없음.
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
| 구글 로그인 페이지로 리다이렉트 | ❌ |
| code → access_token 교환 | ❌ |
| 사용자 정보 가져오기 | ❌ |
| 로그인 세션 생성 | ❌ (JWT 쓴다면 우리가 커스터마이징) |
커스터마이징하는 것들
| 유저 DB 저장/조회 | OAuth2UserService or SuccessHandler |
| JWT 발급 | JwtTokenProvider |
| 프론트로 리디렉트 | OAuth2LoginSuccessHandler |
프론트
window.location.href = 'http://localhost:8080/oauth2/authorization/google';
Spring Security 설정
.oauth2Login(oauth -> oauth
.successHandler(oAuth2LoginSuccessHandler)
)
성공 핸들러
OAuth2User user = (OAuth2User) authentication.getPrincipal();
String email = user.getAttribute("email");
// DB 저장 or 조회
// JWT 생성
// 프론트로 전달
흐름 단계
| 1단계 | /oauth2/authorization/google 접속 (자동 생성됨) |
| 2단계 | 구글 로그인 후 콜백 → OAuth2User 생성 |
| 3단계 | OAuth2LoginSuccessHandler 에서 DB 저장 & JWT 발급 |
| 4단계 | 클라이언트로 JWT 전달 (리다이렉트 or 쿠키) |
전체 흐름 요약
[1] 프론트: 구글 로그인 버튼 클릭 → Spring 서버 리디렉트
[2] 백엔드: 로그인 성공 → JWT 발급 + 리다이렉트
[3] 프론트: 리디렉트된 URL에서 accessToken 추출 → 저장
클라이언트 측
1. 로그인 버튼 (리디렉트)
// src/pages/auth/GoogleLoginButton.tsx
export const GoogleLoginButton = () => {
const handleLogin = () => {
window.location.href = 'http://localhost:8080/oauth2/authorization/google';
};
return (
<button onClick={handleLogin}>
구글 로그인
</button>
);
};
/oauth2/authorization/google ← 이건 Spring Security가 자동으로 만들어주는 URL!
2. 로그인 성공 후 리디렉트 받을 페이지
Spring 서버에서 아래처럼 리디렉션하면
response.sendRedirect("http://localhost:5173/oauth/success?token=" + accessToken);
→ 프론트에서는 이 페이지를 만들어서 token 추출 → 저장 → 리다이렉트 하면 됨!
// src/pages/oauth/GoogleSuccessPage.tsx
import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuthStore } from '@/stores/authStore';
import axios from 'axios';
export const GoogleSuccessPage = () => {
const navigate = useNavigate();
const location = useLocation();
const { setAccessToken } = useAuthStore();
useEffect(() => {
const params = new URLSearchParams(location.search);
const token = params.get('token');
if (token) {
setAccessToken(token);
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
navigate('/home');
} else {
navigate('/auth');
}
}, []);
return <div>로그인 중입니다...</div>;
};
3. 라우터 등록
import { GoogleSuccessPage } from '@/pages/oauth/GoogleSuccessPage';
<Route path="/oauth/success" element={<GoogleSuccessPage />} />
리프레시 토큰을 쿠키로 보내고 있다면?
- 프론트에서 따로 다룰 필요 없이 withCredentials: true 설정만 잘해주면 됨
- 지금은 accessToken만 처리하고 있으니까 localStorage or zustand에 저장하면 OK
결과 흐름
| 로그인 시작 | /oauth2/authorization/google | Spring Security가 구글로 리다이렉트 |
| 로그인 완료 | /login/oauth2/code/google | Spring이 자동으로 처리 |
| JWT 발급 | OAuth2LoginSuccessHandler | JWT → /oauth/success?token=... 리다이렉트 |
| 프론트 수신 | /oauth/success | 토큰 저장 + 페이지 이동 |
Spring Security의 OAuth2Login은 기본적으로:
- 사용자의 인증 상태(Session 또는 SecurityContext)만 유지하지
- JWT 기반의 access + refresh 토큰 발급은 우리가 직접 구성해야 함
즉, 직접 로그인 처리할 때처럼:
ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, refreshCookie.toString())
.body(new TokenResponse(accessToken));
같은 로직을 OAuth 로그인 성공 시에도 동일하게 넣어줘야 함.
해야 할 작업
OAuth2LoginSuccessHandler에서 다음처럼 처리:
@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenService refreshTokenService;
@Override
public void onAuthenticationSuccess(...) {
// 1. OAuth2User에서 사용자 정보 가져오기
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
String email = oAuth2User.getAttribute("email");
// 2. 사용자 DB 저장 or 조회
User user = userService.saveOrGetUser(email);
// 3. access + refresh 토큰 발급
String accessToken = jwtTokenProvider.createAccessToken(user.getId(), user.getEmail());
String refreshToken = jwtTokenProvider.createRefreshToken(user.getId());
// 4. refresh 토큰 저장 (ex: Redis)
refreshTokenService.save(user.getId(), refreshToken, 7 * 24 * 60 * 60);
// 5. refresh 토큰을 쿠키로 응답에 포함
ResponseCookie cookie = jwtTokenProvider.createRefreshTokenCookie(refreshToken);
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
// 6. accessToken은 프론트로 리다이렉트 (query string 전달)
response.sendRedirect("http://localhost:5173/oauth/success?token=" + accessToken);
}
}
@Service
@RequiredArgsConstructor
public class OAuth2UserService {
private final UserRepository userRepository;
public User saveOrGetUser(String email, String name, String provider) {
return userRepository.findByEmail(email)
.orElseGet(() -> userRepository.save(User.oauth(email, name, provider)));
}
}
다시 요약하면
| accessToken 발급 | 일반 로그인 / OAuth 공통 | ✅ 직접 생성 |
| refreshToken 발급 | 일반 로그인에서만 처리됨 | ✅ OAuth에도 직접 추가해야 함 |
| refreshToken 저장 | DB or Redis | ✅ 별도 저장 필요 |
| 쿠키에 담기 | 응답 헤더에서 설정 | ✅ 직접 넣어야 함 (HttpOnly) |
'Java > Spring Boot' 카테고리의 다른 글
| JPQL과 QueryDSL (0) | 2025.03.31 |
|---|---|
| JPA (3) | 2025.03.31 |
| Redis, kafka (0) | 2025.03.27 |
| HTTP 상태 코드 종류 (0) | 2025.03.27 |
| JWT & CRSF (0) | 2025.03.25 |