[ComitChu 개발기] GitHub 소셜 로그인 구현

Suyo·2025년 7월 30일
0

ComitChu

목록 보기
2/5

ComitChu 프로젝트에서는 GitHub 기반의 소셜 로그인을 OAuth 2.0 방식으로 구현했다.React + Spring Boot 기반 SPA 구조이며, 로그인 흐름은 Authorization Code Grant 방식에 따라 구성했다.
이 글에서는 실제 프론트 코드 기반으로, 로그인 버튼 클릭부터 인증 처리까지의 과정을 정리한다.


1. 로그인 버튼 클릭 → GitHub으로 이동

const handleGitHubLogin = () => {
  window.location.href = "https://www.comitchu.shop/oauth2/authorization/github";
};
  • 버튼 클릭 시 백엔드가 미리 설정한 GitHub 인증 주소로 이동한다.
  • 이 URL은 Spring Security가 자동으로 생성한 OAuth 인증 엔드포인트다.

2. GitHub 인증 완료 → 백엔드로 code 전달

GitHub에서 로그인과 권한 허용을 완료하면 아래 URL로 리다이렉트된다.

https://www.comitchu.shop/login/oauth2/code/github?code=abcdef123
  • 이때 전달되는 code는 백엔드가 access token으로 교환할 임시 인증 코드다.

3. Spring Security가 인증 자동 처리

Spring Security는 아래 과정을 자동으로 처리한다:

  1. URL에서 code 값을 추출한다.
  2. application.yml에 설정된 client_id, client_secret과 함께 GitHub에 access token을 요청한다.
  3. 받은 access token으로 사용자 정보를 요청한다.
  4. GitHub 사용자 정보를 기반으로 OAuth2User 객체를 생성한다.
  5. 설정한 OAuthSuccessHandler로 전달해 후처리를 진행한다.
.oauth2Login(oauth -> 
    oauth.successHandler(oAuthSuccessHandler)
)

4. OAuthSuccessHandler에서 JWT 발급 및 리다이렉트

response.addCookie(jwtCookie);
response.sendRedirect("https://www.comitchu.shop");
  • OAuthSuccessHandler.java에서 JWT를 생성하고, HttpOnly 쿠키로 브라우저에 전달한다.
  • 이후 사용자는 프론트엔드의 ComitChu 랜딩페이지로 리다이렉트된다.
  • JWT는 JavaScript에서 접근할 수 없도록 설정되어 있어 보안성이 높다.

5. 프론트엔드는 로그인 상태를 어떻게 유지하는가?

GitHub 로그인 후, 백엔드는 JWT를 HttpOnly 쿠키로 발급하고, 사용자를 comitchu 페이지로 리다이렉트시킨다.
이때 프론트엔드는 JWT를 직접 다루지 않고, 상태를 확인하기 위한 API 호출을 통해 로그인 여부를 판단한다.

아래는 프론트엔드 상태 관리를 담당하는 코드 구성이다.


5-1. Axios 클라이언트 설정

import axios from "axios";

const apiClient = axios.create({
  baseURL: "/api", // 백엔드 프록시 경로
  withCredentials: true, // 쿠키 포함 필수 설정
});

export default apiClient;
  • withCredentials: true 설정은 백엔드에서 발급한 HttpOnly 쿠키를 포함한 요청을 보낼 때 반드시 필요하다.
  • 이 설정이 없으면 브라우저가 쿠키를 백엔드에 포함하지 않는다 → 인증 실패

5-2. Zustand를 활용한 사용자 상태 관리

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 역시 쿠키 기반 인증 구조를 유지하며 로그아웃 처리 후 상태를 초기화한다.

5-3. 최초 진입 시 사용자 정보 조회

// src/App.tsx
function App() {
  const { fetchUser } = useUserStore();

  useEffect(() => {
    fetchUser();
  }, [fetchUser]);
  • App 컴포넌트 마운트 시 fetchUser()를 호출한다.
  • 이 시점에서 쿠키가 브라우저에 존재한다면 자동으로 전송되어 인증이 완료되고, 사용자 정보가 상태에 저장된다.
  • 즉, 앱 전체가 시작될 때 로그인 여부를 판별하고 초기화한다.

5-4. 로그인 여부에 따라 라우팅 분기

<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에서는 userStoreisLoggedIn 값을 확인해 인증되지 않은 사용자는 접근을 차단한다.

5-5. 실제 흐름 요약

  1. 사용자가 GitHub 로그인 → 백엔드가 JWT를 HttpOnly 쿠키에 저장 후 comitchu 사이트로 리다이렉트
  2. App이 로드되며 fetchUser() 호출 → /api/user/me 요청으로 사용자 정보 확인
  3. 상태 저장 → isLoggedIn: true일 경우 대시보드 진입 허용
  4. 로그아웃 시 쿠키 삭제 및 상태 초기화 → 자동 로그아웃

이렇게 ComitChu 프로젝트는 로그인 후 상태를 직접 다루지 않고, JWT 기반 쿠키 인증과 API 응답 결과만으로 로그인 여부를 관리하는 구조다.
상태 관리 라이브러리(Zustand), axios 설정, 초기 렌더링 타이밍, 라우팅 보호까지 모두 자연스럽게 연결되어 있어 유지보수가 쉬운 구조다.


6. 이 구조를 선택한 이유

  1. 보안성 확보

    • client_secret을 프론트에 노출하지 않음
    • JWT를 HttpOnly 쿠키에 저장하여 XSS에 안전
  2. 구현 간소화

    • Spring Security의 자동 인증 처리 기능을 활용
  3. SPA에 적합한 흐름

    • 인증 후 프론트로 리다이렉트하여 SPA 환경을 유지

7. 다른 방식과 비교

항목ComitChu 방식 (백엔드 중심)프론트 처리 방식
redirect_uri백엔드 주소프론트 주소
인증 처리 주체Spring Security프론트 → API
JWT 저장 위치HttpOnly 쿠키다양함
보안성높음다소 취약 (XSS 가능)
구조자동화 활용유연한 라우팅 처리 가능

8. 마무리


ComitChu 프로젝트는 GitHub 로그인을 React 기반 SPA 구조에서 보안성과 유지보수성을 모두 고려해 구현했다. 프론트는 로그인 요청만 보내고, 인증의 핵심 로직은 전적으로 백엔드(Spring Security)가 담당한다. 특히 이번 구현 방식은 기존에 경험했던 다른 소셜 로그인 방식들과 확연히 달랐다.
지금까지는 프론트에서 session이나 localStorage에 access token과 refresh token을 저장하거나, OAuth 인증 후 code를 프론트에서 받아 다시 백엔드로 전달하는 구조를 사용해왔다. 하지만 이번에는 redirect URI부터 access token 요청, 사용자 정보 조회, JWT 발급까지 모든 과정을 백엔드가 직접 처리하는 구조를 선택했다.
이번 기회에 이 방식을 적용해보니 무척이나 편했다. 그리고 무엇보다, 이런 구조를 함께 고민하고 구현한 멋진 팀원들과 협업하는 일은 역시 좋은 일이다.

profile
Mee-

0개의 댓글