1. 소셜 로그인, 도대체 뭐지?
처음에는 단순히 api로 구현되어 있을테니 간단한 작업으로 알았습니다.
하지만 실제로 구현해보니 OAuth2, 토큰, 리디렉션, CORS, 프론트-백 분리 구조 등 복잡한 요소들이 얽혀 있었습니다🥹
2. 기존 로그인 방식과의 차이점
| 인증 정보 | ID / PW 직접 입력 | 외부 서비스 로그인 |
| 토큰 처리 | 직접 발급/검증 구현 | OAuth2 기반 토큰 전달 |
| 장점 | 커스터마이징 자유 | 빠른 가입, 사용자 접근성 |
| 단점 | 유지 보수/보안 직접 관리 | OAuth 플로우 이해 필요 |
3. 소셜 로그인 구조 이해하기
1) 일반 로그인 흐름
1. 프론트엔드 → 백엔드
사용자가 아이디와 비밀번호를 입력하면, 프론트가 그걸 백엔드에 전달합니다.
2. 백엔드 → 프론트엔드
백엔드는 입력한 아이디/비밀번호가 맞는지 확인하고,
맞으면 다음 3가지를 보내줩니다.
- AccessToken (열쇠 같은 거, 유효시간 짧음)
- RefreshToken (열쇠 새로 만드는 열쇠, 유효시간 김)
- AccessToken 만료 시간
그리고 RefreshToken은 서버(DB)에 저장합니다.
왜냐하면 나중에 새로운 AccessToken을 줄 때, 이게 진짜 사용자 건지 확인 필수!!!!
3. 프론트엔드 → API 요청할 때
사용자는 로그인 후 서비스 여러 기능을 이용하게 되죠?
이때마다 매번 AccessToken을 HTTP 헤더에 담아서 요청합니다.
Authorization: Bearer <AccessToken>
4. 백엔드 → 요청 처리
서버는 받은 AccessToken이 유효한지 확인하고, 문제가 없다면 해당 API 기능을 실행합니다.
5. AccessToken이 곧 만료될 때?
AccessToken은 보통 15분~1시간 내외로 금방 만료됩니다.
만약 남은 시간이 30초도 안 남았다면, 프론트는 이렇게 합니다:
- "이 열쇠(AccessToken) 곧 만료되니까 새 걸 주세요!"
- → 대신 진짜 주인이라는 걸 증명하려고 RefreshToken을 같이 보냄
6. 백엔드 → 새 AccessToken 발급
서버는 RefreshToken이 DB에 저장된 것과 같은지 확인하고, 맞으면 새로운 AccessToken + 만료시간을 만들어서 보내줩니다.
7. 프론트엔드 → 새 AccessToken 저장
받은 새로운 AccessToken을 저장해두고, 이후 API 요청에 다시 사용합니다.
비유로 설명하자면…
- AccessToken = 건물 출입증 (단기 유효)
- RefreshToken = 주민등록증 (신분 확인용, 유효기간 김)
- 출입증이 곧 만료되면, 주민등록증을 보여주고 새 출입증을 받는 구조입니다.
4. 실제 코드로 구현한 소셜 로그인 (LiveStudy 프로젝트 기준)
.env 설정:
VITE_API_BASE_URL=https://api.live-study.com
VITE_GOOGLE_CLIENT_ID=...
VITE_KAKAO_CLIENT_ID=...
VITE_NAVER_CLIENT_ID=...
소셜 로그인 처리 방법 정리 (Google, Kakao, Naver)
// 소셜 로그인 URL 설정
const VITE_API_BASE_URL = import.meta.env.VITE_API_BASE_URL!;
// 구글 로그인
export const GOOGLE_AUTH_URL =
'https://accounts.google.com/o/oauth2/v2/auth?' +
new URLSearchParams({
client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID!,
redirect_uri: `${VITE_API_BASE_URL}/login/oauth2/code/google`, // 백엔드에서 지정한 구글 콜백 주소
response_type: 'code',
scope: 'openid profile email',
access_type: 'offline',
prompt: 'consent',
});
// 카카오 로그인
export const KAKAO_AUTH_URL =
'https://kauth.kakao.com/oauth/authorize?' +
new URLSearchParams({
client_id: import.meta.env.VITE_KAKAO_CLIENT_ID!,
redirect_uri: `${VITE_API_BASE_URL}/login/oauth2/code/kakao`,
response_type: 'code',
});
// 네이버 로그인
export const NAVER_AUTH_URL =
'https://nid.naver.com/oauth2.0/authorize?' +
new URLSearchParams({
client_id: import.meta.env.VITE_NAVER_CLIENT_ID!,
redirect_uri: `${VITE_API_BASE_URL}/login/oauth2/code/naver`,
response_type: 'code',
state: 'liveStudyStateToken',
});
소셜 로그인 버튼 UI 구현과 OAuth 연동 정리
<a
href={GOOGLE_AUTH_URL}
className="w-full py-3 bg-gray-100 border border-gray-300 rounded-xl text-body1_R text-gray-500 hover:bg-gray-200 text-center"
>
Google 로그인
</a>
<a
href={KAKAO_AUTH_URL}
className="w-full py-3 bg-gray-100 border border-gray-300 rounded-xl text-body1_R text-gray-500 hover:bg-gray-200 text-center"
>
Kakao 로그인
</a>
<a
href={NAVER_AUTH_URL}
className="w-full py-3 bg-gray-100 border border-gray-300 rounded-xl text-body1_R text-gray-500 hover:bg-gray-200 text-center"
>
Naver 로그인
</a>
5. 내가 직접 겪은 문제들...
문제 1: "구글/카카오 로그인 후 화면이 안 넘어감"
- 증상: 인증까지 했는데 이후 리디렉션이 안 됨
- 원인: Spring Security에서 success handler 또는 defaultSuccessUrl 설정 누락
- 해결: OAuth2AuthenticationSuccessHandler에 sendRedirect() 추가
문제 2: 환경변수 설정 오류
- 증상: 로컬에서는 되는데 배포 시 안 됨
- 원인: VITE_API_BASE_URL 값이 환경마다 달라야 하는데 고정돼 있었음
- 해결: .env.development, .env.production 분리하여 처리