ComitChu 프로젝트에서는 GitHub 기반의 소셜 로그인을 OAuth 2.0 방식으로 구현했다.React + Spring Boot 기반 SPA 구조이며, 로그인 흐름은 Authorization Code Grant 방식에 따라 구성했다.
이 글에서는 실제 프론트 코드 기반으로, 로그인 버튼 클릭부터 인증 처리까지의 과정을 정리한다.
const handleGitHubLogin = () => {
window.location.href = "https://www.comitchu.shop/oauth2/authorization/github";
};
code
전달GitHub에서 로그인과 권한 허용을 완료하면 아래 URL로 리다이렉트된다.
https://www.comitchu.shop/login/oauth2/code/github?code=abcdef123
Spring Security는 아래 과정을 자동으로 처리한다:
application.yml
에 설정된 client_id
, client_secret
과 함께 GitHub에 access token을 요청한다.OAuth2User
객체를 생성한다.OAuthSuccessHandler
로 전달해 후처리를 진행한다..oauth2Login(oauth ->
oauth.successHandler(oAuthSuccessHandler)
)
response.addCookie(jwtCookie);
response.sendRedirect("https://www.comitchu.shop");
OAuthSuccessHandler.java
에서 JWT를 생성하고, HttpOnly
쿠키로 브라우저에 전달한다.ComitChu 랜딩페이지
로 리다이렉트된다.GitHub 로그인 후, 백엔드는 JWT를 HttpOnly
쿠키로 발급하고, 사용자를 comitchu 페이지로 리다이렉트시킨다.
이때 프론트엔드는 JWT를 직접 다루지 않고, 상태를 확인하기 위한 API 호출을 통해 로그인 여부를 판단한다.
아래는 프론트엔드 상태 관리를 담당하는 코드 구성이다.
import axios from "axios";
const apiClient = axios.create({
baseURL: "/api", // 백엔드 프록시 경로
withCredentials: true, // 쿠키 포함 필수 설정
});
export default apiClient;
withCredentials: true
설정은 백엔드에서 발급한 HttpOnly 쿠키를 포함한 요청을 보낼 때 반드시 필요하다.import { create } from "zustand";
import apiClient from "../api";
interface User {
userName: string;
avatarUrl: string;
pet?: Pet;
}
interface UserState {
user: User | null;
isLoggedIn: boolean;
fetchUser: () => Promise<void>;
logout: () => Promise<void>;
}
const useUserStore = create<UserState>((set) => ({
user: null,
isLoggedIn: false,
fetchUser: async () => {
try {
const response = await apiClient.get("/user/me");
if (response.data.success) {
const { userName, avatarUrl } = response.data.data;
set({ user: { userName, avatarUrl }, isLoggedIn: true });
} else {
set({ user: null, isLoggedIn: false });
}
} catch (error) {
// 대충 에러 처리
}
},
logout: async () => {
try {
await apiClient.post("/user/logout");
set({ user: null, isLoggedIn: false });
} catch (error) {
// 대충 에러 처리
}
},
}));
fetchUser
함수는 /api/user/me
엔드포인트를 호출해 현재 로그인된 사용자 정보를 불러온다.HttpOnly
쿠키를 통해 사용자를 식별하며, 성공 시 사용자 객체를 반환한다.user
, isLoggedIn
상태를 전역에서 관리할 수 있다.logout
역시 쿠키 기반 인증 구조를 유지하며 로그아웃 처리 후 상태를 초기화한다.// src/App.tsx
function App() {
const { fetchUser } = useUserStore();
useEffect(() => {
fetchUser();
}, [fetchUser]);
fetchUser()
를 호출한다.<Routes>
<Route path="/" element={<Landing />} />
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/setting" element={<Setting />} />
</Route>
<Route path="*" element={<Error />} />
</Routes>
/dashboard
, /setting
과 같은 민감한 경로는 ProtectedRoute
로 감싼다.ProtectedRoute
에서는 userStore
의 isLoggedIn
값을 확인해 인증되지 않은 사용자는 접근을 차단한다.fetchUser()
호출 → /api/user/me
요청으로 사용자 정보 확인isLoggedIn: true
일 경우 대시보드 진입 허용이렇게 ComitChu 프로젝트는 로그인 후 상태를 직접 다루지 않고, JWT 기반 쿠키 인증과 API 응답 결과만으로 로그인 여부를 관리하는 구조다.
상태 관리 라이브러리(Zustand), axios 설정, 초기 렌더링 타이밍, 라우팅 보호까지 모두 자연스럽게 연결되어 있어 유지보수가 쉬운 구조다.
보안성 확보
구현 간소화
SPA에 적합한 흐름
항목 | ComitChu 방식 (백엔드 중심) | 프론트 처리 방식 |
---|---|---|
redirect_uri | 백엔드 주소 | 프론트 주소 |
인증 처리 주체 | Spring Security | 프론트 → API |
JWT 저장 위치 | HttpOnly 쿠키 | 다양함 |
보안성 | 높음 | 다소 취약 (XSS 가능) |
구조 | 자동화 활용 | 유연한 라우팅 처리 가능 |
ComitChu 프로젝트는 GitHub 로그인을 React 기반 SPA 구조에서 보안성과 유지보수성을 모두 고려해 구현했다. 프론트는 로그인 요청만 보내고, 인증의 핵심 로직은 전적으로 백엔드(Spring Security)가 담당한다. 특히 이번 구현 방식은 기존에 경험했던 다른 소셜 로그인 방식들과 확연히 달랐다.
지금까지는 프론트에서 session이나 localStorage에 access token과 refresh token을 저장하거나, OAuth 인증 후 code를 프론트에서 받아 다시 백엔드로 전달하는 구조를 사용해왔다. 하지만 이번에는 redirect URI부터 access token 요청, 사용자 정보 조회, JWT 발급까지 모든 과정을 백엔드가 직접 처리하는 구조를 선택했다.
이번 기회에 이 방식을 적용해보니 무척이나 편했다. 그리고 무엇보다, 이런 구조를 함께 고민하고 구현한 멋진 팀원들과 협업하는 일은 역시 좋은 일이다.