

📌 들어가며
오늘의 학습 목표: OAuth 2.0의 개념을 이해하고, Spring Security로 카카오/구글/네이버 소셜 로그인 구현하기
안녕하세요! 오늘은 OAuth 2.0 소셜 로그인을 공부했습니다.
솔직히 말하면, 처음에는 "소셜 로그인? 그냥 카카오 API 호출하면 되는 거 아니야?"라고 생각했어요. 근데 막상 구현하려고 보니까 모르는 용어가 쏟아지더라고요.
"Authorization Code? Access Token? Resource Server? 이게 다 뭐야..."
그래서 오늘은 제가 OAuth 2.0을 이해하게 된 과정을 공유해볼게요. 코드보다는 **"왜 이렇게 동작하는지"**에 집중해서 정리했습니다!
🤔 왜 소셜 로그인을 해야 할까?
직접 회원가입을 만들었을 때 느낀 점
프로젝트 초반에 이메일 + 비밀번호 회원가입을 구현했어요. 근데 막상 만들고 나니까 이런 생각이 들더라고요.
나라면 이 서비스에 가입할까?
1. 아이디 입력 (중복 체크...)
2. 비밀번호 입력 (대문자, 특수문자 포함해야 하고...)
3. 비밀번호 확인 (아 귀찮아...)
4. 이메일 입력 (또 입력이야?)
5. 이메일 인증 (메일함 열어서 인증번호 복사...)
6. 휴대폰 인증까지...?
→ 😩 "그냥 안 쓸래..."
실제로 통계를 찾아보니까, 회원가입 폼이 길수록 이탈률이 급격히 증가한다고 해요. 반면에 소셜 로그인은 "카카오로 시작하기" 버튼 한 번이면 끝이죠.
보안 측면에서도 유리하다
처음에는 "우리가 직접 비밀번호를 관리하는 게 더 안전하지 않나?"라고 생각했어요. 근데 생각해보니까 정반대더라고요.
직접 관리할 때:
- 비밀번호 암호화 직접 구현 (BCrypt? Argon2?)
- 비밀번호 찾기 기능 구현
- 해킹당하면 우리 책임
- 사용자들은 어차피 같은 비밀번호 돌려씀...
소셜 로그인:
- 비밀번호를 아예 저장 안 함!
- 카카오/구글이 보안 담당 (전문가들이 관리)
- 2FA 같은 고급 보안도 그쪽이 알아서
- 해킹당해도 비밀번호 유출 없음
**"가장 안전한 비밀번호는 저장하지 않는 비밀번호"**라는 말이 와닿았어요.
📚 OAuth 2.0, 처음에 뭐가 헷갈렸나
용어가 너무 많았다
처음 OAuth 2.0 문서를 봤을 때 진짜 멘붕이었어요.
Resource Owner, Client, Authorization Server,
Resource Server, Access Token, Refresh Token,
Authorization Code, Redirect URI, Scope...
🤯 이게 다 뭐야?
근데 하나씩 정리하니까 생각보다 단순하더라고요.
내가 이해한 방식으로 정리:
용어 쉽게 말하면 예시
| Resource Owner | 진짜 주인 (사용자) | 카카오 계정 가진 나 |
| Client | 우리가 만든 앱 | 내 쇼핑몰 서비스 |
| Authorization Server | 로그인 처리하는 곳 | 카카오 로그인 서버 |
| Resource Server | 정보 저장된 곳 | 카카오 프로필 API |
| Access Token | 출입증 | "이 사람 인증됐어요" 증표 |
처음에 제일 헷갈렸던 건 **"Client"**라는 용어였어요. 보통 클라이언트라고 하면 프론트엔드(브라우저)를 떠올리잖아요? 근데 OAuth에서 Client는 **"우리 서비스 전체"**를 의미해요. 카카오 입장에서 우리 서비스가 "손님(Client)"인 거죠.
"왜 바로 토큰 안 주고 Code를 거치지?"
이것도 처음에 이해가 안 됐어요.
내 생각:
"사용자가 카카오 로그인하면 바로 Access Token 주면 되는 거 아냐?
왜 굳이 Authorization Code를 먼저 주고, 그걸로 다시 Token을 요청해?"
이유를 찾아보니까 보안 때문이었어요.

만약 바로 Token을 주면?
1. 사용자가 카카오 로그인
2. 카카오가 Access Token을 URL에 담아서 우리 서버로 리다이렉트
→ http://우리서버/callback?access_token=abc123...
문제: Access Token이 브라우저 주소창에 노출됨!
→ 브라우저 히스토리에 남음
→ 중간에 가로채기 가능
Authorization Code 방식:
1. 사용자가 카카오 로그인
2. 카카오가 Authorization Code를 URL에 담아서 리다이렉트
→ http://우리서버/callback?code=xyz789...
3. 우리 서버가 이 Code로 카카오 서버에 직접 요청
→ "이 Code 맞아? 그럼 Access Token 줘"
→ 서버 ↔ 서버 통신 (브라우저 안 거침!)
4. Access Token은 서버에서만 관리
핵심: Access Token이 브라우저에 절대 노출 안 됨!
이 구조를 이해하고 나니까 "아, 그래서 Code를 거치는구나!"하고 납득이 됐어요.
🔧 구현하면서 겪은 시행착오들
시행착오 1: "Redirect URI가 뭔데 자꾸 에러야?"
처음 카카오 로그인을 연동했을 때, 이런 에러가 떴어요.
KOE006: redirect_uri_mismatch
"URI가 안 맞는다는 건 알겠는데, 뭐가 안 맞는 거지?" 한참을 헤맸어요.
원인: 카카오 개발자 센터에 등록한 URI와 실제 요청 URI가 달랐어요.
카카오에 등록한 URI:
http://localhost:8080/login/oauth2/code/kakao
실제 요청된 URI:
http://localhost:8080/login/oauth2/code/kakao/ ← 끝에 슬래시!
네, 슬래시 하나 때문에 안 됐습니다. 😇
해결: URI는 완전히 똑같아야 해요. 대소문자, 슬래시, 포트 번호까지 전부!
체크리스트:
✅ http vs https
✅ localhost vs 127.0.0.1
✅ 포트 번호 (8080 vs 80)
✅ 경로 끝 슬래시 유무
✅ 오타 (oauth vs oAuth)
시행착오 2: "카카오 이메일이 왜 null이야?"
로그인은 성공했는데, 이메일이 안 넘어와서 당황했어요.
String email = kakaoAccount.get("email"); // null ??
원인: 카카오 개발자 센터에서 이메일 동의항목을 **"선택 동의"**로 해놨더라고요. 사용자가 동의 안 하면 안 줘요.
해결 과정:
1. 카카오 개발자 센터 → 동의항목 설정
2. "이메일"을 "필수 동의"로 변경하려고 함
3. 어? "필수 동의"가 안 눌림...
4. 알고 보니 "비즈 앱 전환"을 해야 필수 동의 가능
5. 비즈 앱 전환 후 해결!
배운 점: 카카오는 개인 앱 vs 비즈 앱에 따라 받을 수 있는 정보가 달라요. 실 서비스라면 비즈 앱 전환이 필수!
시행착오 3: "구글은 되는데 카카오는 왜 안 돼?"
구글 로그인은 application.yml에 client-id, client-secret만 넣으면 바로 됐어요. 근데 카카오는 안 되더라고요.
원인: 구글은 Spring Security가 기본 지원하지만, 카카오/네이버는 Provider 설정을 직접 해줘야 해요.
# 구글: 이것만 있으면 됨 (기본 지원)
spring:
security:
oauth2:
client:
registration:
google:
client-id: xxx
client-secret: xxx
# 카카오: Provider 설정 필요!
registration:
kakao:
client-id: xxx
client-secret: xxx
# ... 여러 설정들
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
배운 점: Spring Security OAuth2가 기본 지원하는 건 Google, Facebook, GitHub, Okta 정도예요. 카카오, 네이버는 직접 Provider 설정을 해야 합니다!
시행착오 4: "각 소셜마다 응답 구조가 다르다고?"
이건 진짜 당황스러웠어요. 로그인해서 사용자 정보를 받아보니까...

// 구글 응답 - 깔끔!
{
"sub": "123456789",
"email": "user@gmail.com",
"name": "홍길동",
"picture": "https://..."
}
// 카카오 응답 - 중첩 구조
{
"id": 123456789,
"kakao_account": {
"email": "user@kakao.com",
"profile": {
"nickname": "홍길동",
"profile_image_url": "https://..."
}
}
}
// 네이버 응답 - response 안에 또 있음
{
"resultcode": "00",
"message": "success",
"response": {
"id": "abc123",
"email": "user@naver.com",
"name": "홍길동",
"profile_image": "https://..."
}
}
"야, 왜 다 다른 거야..." 😭
해결: 소셜 타입별로 파싱 로직을 분기 처리했어요.
핵심 로직:
1. registrationId로 어떤 소셜인지 확인 ("google", "kakao", "naver")
2. 소셜별로 다르게 파싱
- 구글: 바로 email, name 꺼냄
- 카카오: kakao_account → profile 안에서 꺼냄
- 네이버: response 안에서 꺼냄
3. 통일된 형태(OAuthAttributes)로 변환
4. 이후 로직은 동일하게 처리!
배운 점: OAuth 2.0 표준은 "인증 흐름"만 정의하고, 응답 형식은 각 서비스 마음대로예요. 그래서 각 소셜별 API 문서를 꼭 확인해야 해요!
💡 핵심 개념 정리
OAuth 2.0 전체 흐름 (내가 이해한 버전)

1️⃣ 사용자: "카카오로 로그인" 버튼 클릭
2️⃣ 우리 서버 → 카카오: "이 사람 로그인 좀 시켜줘"
(카카오 로그인 페이지로 리다이렉트)
3️⃣ 사용자 → 카카오: 카카오 ID/PW 입력 + 정보 제공 동의
4️⃣ 카카오 → 우리 서버: "인증됐어! 이 Code 가져가"
(Authorization Code를 담아서 우리 서버로 리다이렉트)
5️⃣ 우리 서버 → 카카오: "이 Code로 Token 줘"
(서버 간 직접 통신 - 브라우저 안 거침!)
6️⃣ 카카오 → 우리 서버: "여기 Access Token"
7️⃣ 우리 서버 → 카카오: "이 Token으로 사용자 정보 줘"
8️⃣ 카카오 → 우리 서버: "여기 이메일, 닉네임, 프로필..."
9️⃣ 우리 서버:
- 처음 온 사람? → 회원가입 처리
- 기존 회원? → 로그인 처리
- JWT 발급해서 프론트에 전달
Authorization Code가 중요한 이유
처음에 "그냥 바로 Token 주면 안 돼?"라고 생각했는데, 이유가 있었어요.
핵심 포인트:
Authorization Code:
- 브라우저를 통해 전달 (URL에 노출)
- 일회용 (한 번 쓰면 폐기)
- 짧은 유효시간 (보통 1분)
- 이걸로 할 수 있는 건 없음 (Token 요청만 가능)
Access Token:
- 서버 간 직접 통신으로 전달 (브라우저 모름!)
- 실제로 리소스에 접근 가능
- 유효시간 김 (보통 1시간)
- 노출되면 위험!
→ 노출되어도 상관없는 Code를 먼저 주고,
중요한 Token은 서버끼리만 주고받음!
Spring Security가 해주는 것 vs 내가 해야 하는 것

🤖 Spring Security가 알아서 해주는 것:
✅ 로그인 페이지로 리다이렉트
✅ Authorization Code 받기
✅ Code로 Access Token 요청
✅ Access Token으로 사용자 정보 요청
✅ 세션 관리
→ application.yml 설정만 잘 하면 자동으로 됨!
✋ 내가 직접 해야 하는 것:
📌 사용자 정보 가공 (CustomOAuth2UserService)
- 각 소셜별 응답 파싱
- 우리 DB에 저장할 형태로 변환
📌 회원가입/로그인 처리
- 처음 온 사람인지 확인
- DB에 저장 또는 업데이트
📌 로그인 성공 후 처리 (SuccessHandler)
- JWT 발급
- 프론트엔드로 리다이렉트
🔐 JWT와 조합하기
"소셜 로그인 했으면 끝 아니야?"
처음에는 소셜 로그인만 하면 끝인 줄 알았어요. 근데 생각해보니까...
문제 상황:
1. 사용자가 카카오 로그인 성공
2. "로그인 됐어요!" 상태
3. 사용자가 상품 주문 API 호출
4. 서버: "어... 이 사람 누구지?"
→ 로그인 상태를 유지할 방법이 필요!
해결: 소셜 로그인 성공 후 우리 서비스의 JWT를 발급!
최종 흐름:
1. 카카오 로그인 성공
2. 카카오에서 받은 정보로 우리 DB에 회원 저장
3. 우리 서비스의 JWT 발급 (Access Token + Refresh Token)
4. 프론트엔드에 JWT 전달
5. 이후 API 호출은 이 JWT로!
카카오 Access Token: 카카오 API 호출용 (프로필 정보 등)
우리 JWT: 우리 서비스 API 인증용
→ 두 개는 완전히 다른 거예요!
토큰 전달 방식 고민
JWT를 프론트에 어떻게 전달할지 고민했어요.
방법 1: URL 쿼리 파라미터
http://프론트/callback?accessToken=xxx&refreshToken=xxx
장점: 구현 간단
단점: URL에 토큰 노출, 브라우저 히스토리에 남음
방법 2: 쿠키
Set-Cookie: accessToken=xxx; HttpOnly; Secure
장점: 자동으로 매 요청에 포함
단점: CSRF 공격 가능성
방법 3: POST로 전달
로그인 성공 후 폼 자동 제출
장점: URL에 노출 안 됨
단점: 구현 복잡
내 선택: 일단 방법 1로 구현하고, HTTPS 적용 후 방법 2로 개선 예정!
실제 서비스에서는 보안을 더 강화해야 하지만, 부트캠프 프로젝트 단계에서는 동작하는 것부터 구현하는 게 중요하다고 판단했어요.
🛠️ 실제 구현할 때 주의할 점
1. 환경변수로 민감 정보 관리
절대 하면 안 되는 것:
application.yml에 직접 작성
client-id: abc123def456... ← 이러면 안 됨!
client-secret: xyz789... ← GitHub에 올라감!
올바른 방법:
application.yml:
client-id: ${KAKAO_CLIENT_ID}
client-secret: ${KAKAO_CLIENT_SECRET}
실행 시 환경변수로 주입:
export KAKAO_CLIENT_ID=abc123...
export KAKAO_CLIENT_SECRET=xyz789...
2. 소셜 서비스별 특이사항
서비스 주의할 점
| 카카오 | 이메일 필수 동의하려면 비즈 앱 전환 필요 |
| 구글 | 기본 Provider 지원, 설정 제일 간단 |
| 네이버 | 응답이 response 객체 안에 중첩됨 |
| 애플 | 이메일을 최초 1회만 제공 (저장 필수!) |
3. 동일 이메일 처리
고민했던 상황:
1. 사용자가 구글로 가입 (email: user@gmail.com)
2. 나중에 카카오로 로그인 시도 (같은 이메일)
3. 어떻게 처리해야 하지?
선택지:
A) 별개 계정으로 처리 → 사용자 혼란
B) 같은 계정으로 연결 → 보안 이슈 가능성
C) "이미 구글로 가입되어 있습니다" 안내 → 우리 선택!
💡 오늘 배운 핵심 정리

1️⃣ OAuth 2.0은 "인증 위임"
- 비밀번호를 우리가 관리 안 함
- 카카오/구글이 인증 담당
- 우리는 "누구인지"만 확인
2️⃣ Authorization Code Grant가 안전한 이유
- Access Token이 브라우저에 노출 안 됨
- 일회용 Code로 Token 교환
- 서버 간 직접 통신
3️⃣ Spring Security가 대부분 처리
- 설정만 잘 하면 인증 흐름은 자동
- 우리는 "사용자 정보 처리"에 집중
4️⃣ 각 소셜마다 응답 형식이 다름
- 구글: 바로 접근 가능
- 카카오: kakao_account.profile 안에
- 네이버: response 안에
→ 파싱 로직 분기 처리 필요!
5️⃣ 소셜 로그인 ≠ 우리 서비스 로그인
- 소셜에서 정보 받은 후
- 우리 서비스의 JWT 따로 발급해야 함
😊 좋았던 점 & 😅 아쉬웠던 점
좋았던 점
👍 회원가입 UX 개선
"이메일 입력 → 인증번호 확인 → 비밀번호 설정..."이 "카카오로 시작하기" 한 번으로 줄었어요!
👍 보안 부담 감소
비밀번호 암호화, 비밀번호 찾기, 해킹 대응... 이런 걸 카카오가 대신 해줘요. 우리는 서비스 기능 개발에 집중할 수 있어요.
👍 Spring Security의 편리함
처음에는 복잡해 보였는데, 막상 해보니까 application.yml 설정 + CustomOAuth2UserService 구현만으로 대부분 해결됐어요.
아쉬웠던 점
😢 각 소셜마다 설정이 달라서 삽질
구글은 쉬웠는데, 카카오/네이버는 Provider 설정을 직접 해야 해서 헷갈렸어요. 공식 문서를 꼼꼼히 읽는 습관이 필요하다고 느꼈습니다.
😢 Apple 로그인은 못 해봄
iOS 앱이 있으면 Apple 로그인이 필수인데, 개발자 계정이 필요해서 이번에는 패스했어요. 나중에 꼭 해보고 싶어요!
😢 토큰 보안 고민
URL로 JWT 전달하는 방식이 찝찝해요. 실서비스에서는 HttpOnly 쿠키 방식으로 개선해야 할 것 같아요.
🤔 이번 학습에서 느낀 점
OAuth 2.0을 공부하면서 가장 크게 느낀 건 "왜?"를 이해해야 한다는 거예요.
처음: "Authorization Code? 그냥 외워야지..."
나중: "아! 토큰을 브라우저에 노출 안 시키려고
일회용 Code를 먼저 주는 거구나!"
→ 이해하니까 에러가 나도 원인을 추측할 수 있었어요.
그리고 공식 문서의 중요성도 느꼈어요. 카카오/네이버는 블로그 글마다 설정이 조금씩 달랐는데, 공식 문서가 가장 정확했어요.
🚀 다음 학습 목표
📌 단기
✓ 프로젝트에 소셜 로그인 적용
✓ 프론트엔드와 연동 테스트
📌 중기
✓ Refresh Token으로 Access Token 재발급
✓ 회원 탈퇴 시 소셜 연결 해제
📌 장기
✓ Apple 로그인 추가
✓ HttpOnly 쿠키 방식으로 보안 강화
🎬 마치며
OAuth 2.0을 배우기 전에는 "소셜 로그인? API 호출하면 되는 거 아냐?"라고 단순하게 생각했어요.
근데 직접 구현해보니까, 왜 Authorization Code를 거치는지, 왜 각 소셜마다 설정이 다른지, 왜 우리 JWT를 따로 발급해야 하는지 이해하게 됐어요.
특히 "가장 안전한 비밀번호는 저장하지 않는 비밀번호"라는 말이 와닿았습니다. 사용자 입장에서도 편하고, 개발자 입장에서도 보안 부담이 줄어드니까 소셜 로그인은 선택이 아니라 필수인 것 같아요!

이제 "카카오로 3초 만에 로그인" 기능을 직접 만들 수 있게 됐습니다! 🎉
📚 참고 자료
Tag: #OAuth2 #소셜로그인 #SpringSecurity #카카오로그인 #구글로그인 #네이버로그인 #JWT #TIL #멀티캠퍼스 #유레카3기백엔드 #백엔드개발 #부트캠프후기