🔍 개요
이번 프로젝트에서는 Spring Security와 JWT를 기반으로 인증/인가 시스템을 설계하고, Kakao/Naver 소셜 로그인 기능을 통합 구현하였습니다. 인증 정보를 서버에 저장하지 않는 Stateless 구조로 유지하면서, OAuth2 인증 과정을 WebClient로 커스터마이징하여 JWT 발급 흐름과 통합하였습니다.
🛠️ 환경 설정
1. Kakao / Naver Developers 앱 등록
Kakao
비즈니스 정보 심사를 받아야 전화번호 등 민감정보 사용 가능
실제 서비스 URL이 필요하므로 localhost 환경에서는 비즈니스 심사가 불가능.
테스트앱을 통해 동의항목 등록하여 개발 진행
Naver
등록된 ID(개발자 계정)만 로그인 테스트 가능
2. application.yml OAuth 설정
spring:
security:
oauth2:
client:
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
naver:
...
registration:
kakao:
client-id: ${KAKAO_CLIENT_ID}
...
naver:
...
🔐 로그인 흐름
- 클라이언트가 /oauth2/authorization/kakao 또는 /naver 경로로 접근
- Spring Security가 제공하는 OAuth 동의 화면으로 리다이렉트
- 사용자가 동의 → redirect-uri로 인가 코드(code) 전달
- 백엔드에서 WebClient로 인가 코드를 이용해 Access Token 요청
- Access Token으로 사용자 정보 요청
- 사용자 정보로 회원 조회 또는 자동 회원가입 처리
- JWT(AccessToken, RefreshToken) 발급 후 응답
✅ 구현 포인트
1. WebClient 기반 OAuth 흐름 직접 구현
- Spring Security의 기본 OAuth2LoginFilter는 세션 기반 구조
- JWT 기반 인증 구조(Stateless) 를 사용 중이므로, 소셜 로그인 후에도 직접 JWT를 발급해야 했음
- KakaoAuthService, NaverAuthService 에서 다음 흐름 직접 구현:
- 인가 코드로 AccessToken 요청
- 사용자 정보 조회
- JWT 발급 및 응답
// WebClient로 액세스 토큰 요청
webClient.post()
.uri(oAuthProperties.getProvider().getKakao().getTokenUri())
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(String.class)
.map(oAuthResponseParser::extractAccessToken)
.block();
2. JWT 기반 인증 구조 통합
- 소셜 로그인 / 일반 로그인 모두 같은 방식으로 JWT 발급
- AccessToken은 사용자 인증, RefreshToken은 토큰 재발급 용도
- 모든 사용자는 User 테이블 하나로 관리
3. 사용자 탈퇴 및 소셜 연결 해제
문제: OAuth AccessToken은 사용자 정보 요청 시만 사용 → 연결 해제하려면 토큰을 저장하거나 다시 받아야 함
Kakao
어드민 키 기반 unlink API 제공 → AccessToken 없이 연결 해제 가능
webClient.post()
.uri(kakaoOAuthProperties.getUnlinkUri())
.header(HttpHeaders.AUTHORIZATION, OAuthConstants.KAKAO_ADMIN_AUTH_PREFIX + kakaoOAuthProperties.getAdminKey())
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(Void.class)
.block();
Naver
반드시 OAuth AccessToken 필요 → 현재 구조와 맞지 않아 미지원 (DB 유저 삭제만 처리)
🧠 회고 및 고민
장점
- Stateless 구조로 서버 부담 최소화
- 일반 로그인/소셜 로그인 동일한 JWT 인증 흐름으로 통일
- WebClient로 필요한 필드만 가공하여 불필요한 세션 최소화
단점
- Spring Security의 필터 흐름을 사용하지 않아 복잡도가 증가
- 네이버 unlink 구현 어려움 (OAuth 토큰 저장 필요)
'Dev Projects' 카테고리의 다른 글
[table-now] AccessToken 블랙리스트 기반 토큰 무효화 기능 구현 (0) | 2025.04.22 |
---|---|
[table-now] Refresh Token 저장소를 Redis로 교체하며 인증 구조 고도화하기 (0) | 2025.04.21 |
[Spring Data JPA_일정 관리 앱 Develop] 필수 & 도전 기능 구현 기록 🖋️ (1) | 2025.02.13 |
[Spring JDBC_일정 관리 앱 만들기] 회고 및 트러블 슈팅 (0) | 2025.02.04 |
[Java Project_키오스크 과제] 도전 기능 구현 및 트러블 슈팅 (0) | 2025.01.20 |