1. 도입부 (Introduction)
OAuth2 로그인을 구현할 때 많은 개발자가 간과하는 부분이 있다. 바로 "인가 요청(Authorization Request)을 생성하는 시점"이다. 단순히 라이브러리가 만들어주는 URL로 사용자를 리다이렉트시키는 것만으로는 부족하다.
플랫폼마다(Google, Naver, Kakao 등) 요구하는 특수 파라미터가 다를 수 있고, 특히 보안이 취약한 환경(모바일, SPA)에서는 PKCE(Proof Key for Code Exchange)라는 추가적인 방어막이 필수적이다. 오늘은 인가 요청이 생성되기 직전, 최전방에서 요청을 가공하고 보안을 강화하는 ProviderAwareAuthorizationRequestResolver의 내부 동작과 그 중요성을 심층 분석해 본다.
[Spring Security] OAuth2 로그인 최종장: 토큰 정책과 앱 이동 흐름
이전 게시물에서 유저가 누구인지 판별하고 USER 혹은 NEW_USER라는 깃발(Authority)을 꽂았다면, 이제는 그 깃발을 보고 실제 어떤 열쇠(Token)를 줄 것인지, 그리고 유저를 어떤 문(Redirect)으로 보낼 것
myblog01150.tistory.com
2. 큰 흐름: 인가 요청부터 토큰 발급까지
사용자가 로그인 버튼을 누르는 순간부터 서버 내부에서는 정교한 '요청 조립' 과정이 일어난다.

전체 시퀀스 다이어그램

3. 핵심 개념/이론
① AuthorizationRequestResolver란?
OAuth2 인가 서버(Google 등)로 보내기 위한 최종 요청 객체(OAuth2AuthorizationRequest)를 생성하는 커스텀 지점이다. 스프링 시큐리티의 기본값 외에 우리 서비스만의 정책을 녹여낼 수 있는 마지막 문지기라고 생각하면 된다.
② 왜 Provider별 파라미터가 필요한가? (UX의 일관성)
- Google: 사용자가 항상 계정을 선택하게 하고 싶다면 prompt=select_account가 필요하다.
- Naver: 사용자가 다시 인증하게 하려면 auth_type=reauthenticate가 필요하다. 이처럼 공급자마다 다른 '언어'를 하나로 통일하여 관리해야 한다.
③ PKCE(Proof Key for Code Exchange)의 본질
PKCE는 "인가 코드 탈취(Authorization Code Interception)" 공격을 원천 봉쇄하기 위한 기술이다.

4. 상세 코드 분석: 보안과 운영의 핵심 지점
A) Provider별 전략적 파라미터 주입
인가 요청 시 각 Provider가 제공하는 특수 기능을 활성화한다.
- 파일 위치: auth/config/ProviderAwareAuthorizationRequestResolver.java
// registrationId에 따른 파라미터 분기 처리
switch (registrationId.toLowerCase()) {
case "google" -> {
// 구글 로그인 시 항상 계정 선택 창을 띄움
additionalParams.put("prompt", "select_account");
}
case "naver" -> {
// 네이버 로그인 시 항상 재인증을 요구하여 보안 강화
additionalParams.put("auth_type", "reauthenticate");
}
}
- 운영적 가치: 특정 플랫폼에서 로그인 세션이 꼬이거나, 사용자가 원치 않는 계정으로 자동 로그인되는 CS를 사전에 방지할 수 있다.
B) PKCE 주입 (보안의 핵심)
가장 중요한 보안 로직이다. code_verifier를 생성하고, 이를 해싱하여 code_challenge를 만든다.

// 1. 보안용 난수(verifier) 생성
String codeVerifier = generateCodeVerifier();
// 2. verifier를 해싱하여 challenge 생성 (인가 서버 전달용)
String codeChallenge = createCodeChallenge(codeVerifier);
// 인가 요청 파라미터에 challenge 주입
additionalParams.put("code_challenge", codeChallenge);
additionalParams.put("code_challenge_method", "S256");
// 나중(토큰 교환 시)을 위해 verifier를 속성(attributes)에 임시 보관
attributes.put("code_verifier", codeVerifier);
5. 실패 시나리오 및 디버깅 포인트
현장에서 가장 많이 마주치는 오류 상황들이다.
| 현상 | 원인 | 해결 방안 |
| 로그인 창은 뜨는데, 동의 후 에러 발생 | code_challenge 오타 혹은 지원하지 않는 알고리즘 | method가 S256인지 확인, 오타 체크 |
| 토큰 교환 단계에서 401/400 에러 | 세션에 저장된 code_verifier가 유실됨 | 세션 클러스터링 설정 혹은 쿠키 만료 시간 확인 |
| 특정 기기에서만 로그인 실패 | code_verifier 생성 시 사용하는 난수 알고리즘 호환성 | SecureRandom 등 표준 라이브러리 사용 확인 |
6. 실무 관점의 개선 포인트 (Next Step)
- 전략 패턴(Strategy Pattern) 도입: switch-case문이 너무 길어진다면 ProviderParameterStrategy 인터페이스를 만들어 분리하자. 새로운 소셜 로그인이 추가되어도 기존 코드를 수정하지 않는 OCP(개방-폐쇄 원칙)를 지킬 수 있다.
- 보안 로그 강화: 인가 요청이 생성될 때 어떤 파라미터가 들어갔는지(단, 민감정보인 code_verifier는 제외!) 로그를 남기면 장애 대응 속도가 획기적으로 빨라진다.
- Fail-Fast 설계: code_verifier 생성이 불가능한 환경이거나 필수 파라미터가 누락된 경우, Provider로 보내기 전에 예외를 발생시켜 사용자에게 명확한 에러 페이지를 보여주어야 한다.
7. 한 줄 정리 (Summary)
"인가 요청 커스터마이징은 단순한 편의 기능이 아니라, 플랫폼별 UX 격차를 해소하고 PKCE를 통해 인가 코드 탈취 리스크를 원천 차단하는 최전방 방어선이다."
단순히 로그인이 된다고 끝나는 것이 아니라, 이 단계에서 보안 규약을 얼마나 철저히 지키느냐가 전체 시스템의 보안 수준을 결정한다. 이제 브라우저 주소창에 찍히는 OAuth2 URL 속의 code_challenge 파라미터가 얼마나 든든한 방패인지 이해할 수 있을 것이다. 🚀
'Spring > Security' 카테고리의 다른 글
| [Spring Security] 관리자 접근 제어의 모든 것: 인증(401)과 권한(403)의 철저한 격리 및 이중 방어 전략 (0) | 2026.05.22 |
|---|---|
| [Spring Security] OAuth2 로그인 최종장: 토큰 정책과 앱 이동 흐름 (0) | 2026.05.11 |
| [Spring Security] OAuth2 로그인 성공 후: 신규와 기존 사용자를 가르는 설계의 모든 것 (0) | 2026.05.11 |
| [Spring Security] JwtAuthenticationFilter 해부: 인증 문지기의 설계와 역할 분리 (1) | 2026.05.09 |
| [Spring Security] 운영 보안 설계 : subject 규약과 Access/Refresh 토큰 분리 (0) | 2026.05.09 |