🔍 문제 정의 (Why)
- Redis 사용 중 발생하는 에러에 대한 대응이 없음
- 소셜 로그인 WebClient 사용 중 발생하는 에러에 대한 대응이 없음
- HTTP 4xx/5xx 응답 또는 네트워크 단절 시 사용자에게 불명확한 에러 발생
- S3 Presigned URL 생성 중 예외 발생 시 로그나 예외 처리가 없음
✅ 해결 방법 & 기술 선택 (How)
💡 로그 스타일
- 예외 로그의 출처 구분을 위해 prefix 지정
[Redis][OAuth][S3]
💡ErrorCode 리팩토링
- AUTH와 USER에서 사용하던 에러코드 목록을 도메인별로 분리 (
AUTH & USER→AUTH,USER) - 네이밍 개선
- 예)
FAILED_TO_PARSE_OAUTH_TOKEN→OAUTH_RESPONSE_PARSING_FAILED
- 예)
💡 Redis 관련 처리
1. RefreshToken 저장 실패 시 예외 처리
- 대상메서드:
createRefreshToken(...) - 이유
- 리프레시 토큰이 있어야만 토큰 재발급 가능함.
- create 시 Redis 저장 실패하면 → validate 시 무조건 실패하게 됨
- 즉, 초기 저장 실패를 그냥 무시하면, 유저는 다음 요청에서 무조건 에러를 만나게 되는 구조
- 의사결정: 저장 실패 시, 명시적으로 예외를 던져 전체 흐름 중단
- Before
redisTemplate.opsForValue().set(...);
return refreshToken;
- After
try {
redisTemplate.opsForValue().set(...);
} catch (RedisConnectionFailureException | RedisSystemException e) {
log.warn("[Redis] RefreshToken Redis 저장 실패: {}", e.getMessage());
throw new HandledException(ErrorCode.REDIS_CONNECTION_ERROR);
}
return refreshToken;
2. RefreshToken 검증 중 Redis 장애 시 예외 처리
- 대상 메서드:
validateRefreshToken(...) - 의사결정
- Redis 장애로
get()이 실패하면 흐름 전체가 무력화됨 → 예외로 중단
- Redis 장애로
3. RefreshToken 삭제 실패 시 로그 기록
- 대상 메서드:
deleteRefreshToken(...) - 의사결정
- 삭제 실패는 서비스 흐름 중단 요건이 아님 → 예외는 로깅만
4. AccessToken 블랙리스트 등록 실패 시 로그 기록
- 대상 메서드:
addToBlacklist(...) - 의사결정
- 실패 시 보안 강화는 못 하지만, 기능 자체 중단은 피해야 하므로 로그만 기록
💡 OAuth 소셜 로그인 리팩토링
1. WebClient 응답 코드에 따른 에러 처리 추가
- 대상 메서드:
- Kakao:
getKakaoAccessToken,getKakaoUserInfo,unlinkKakaoByAdminKey - Naver:
getNaverAccessToken,getNaverUserInfo
- Kakao:
- 이유: HTTP 오류 상태에서도 block()까지 내려가면서 적절한 예외 핸들링 없이 종료됨
- 의사결정:
.onStatus()를 활용하여 상태 코드별로 예외 처리
.onStatus(HttpStatusCode::is4xxClientError, res -> {
log.warn("[OAuth] 카카오 인가코드 오류 (4xx): {}", res.statusCode());
return Mono.error(new HandledException(ErrorCode.OAUTH_TOKEN_REQUEST_FAILED));
})
2. 네트워크 오류 대응
- WebClient 요청이 아예 실패할 경우 (
WebClientRequestException) 예외를 잡아 로그 기록 및 예외 처리
} catch (WebClientRequestException e) {
log.error("[OAuth] WebClient 요청 실패", e);
throw new HandledException(ErrorCode.OAUTH_PROVIDER_UNREACHABLE);
}
3. 응답 파싱 공통 처리
- 중복되는
access_token추출 로직을OAuthResponseParser.extractAccessToken()으로 유틸 클래스화 - Bean으로 등록하여 Kakao/NaverAuthService에서 주입해서 사용
- 위치:
global.util.OAuthResponseParser
.map(oAuthResponseParser::extractAccessToken)
💡 S3 Presigned URL 생성 예외 핸들링 추가
- 대상 메서드:
generatePresignedUrl(...) - presigner 동작 중 발생할 수 있는
S3Exception,SdkClientException,IllegalArgumentException예외를 핸들링
} catch (S3Exception | SdkClientException | IllegalArgumentException e) {
log.error("[S3] Presigned URL 생성 중 예외 발생", e);
throw new HandledException(ErrorCode.S3_PRESIGNED_URL_CREATION_FAILED);
}
📝 상세 수정 사항
https://github.com/spring-team-7/table-now/pull/147
[refactor] Redis/OAuth/S3 예외 처리 및 테스트 코드 개선 by mannaKim · Pull Request #147 · spring-team-7/table-now
🔗 Issue Number close #140 close #141 📝 작업 내역 Redis 관련 처리 createRefreshToken() 내 Redis 저장 실패 시 예외 처리 및 로그 기록 validateRefreshToken() 내 Redis 장애 시 예외 처리 deleteRefreshToken() 내 Redis 삭제
github.com
🧠 회고
이번 작업은 "예외 처리를 얼마나 신경 써야 하는가?"에 대해 다시 생각해보는 계기였습니다.
처음엔 단순히 로그만 찍고 넘어가도 되지 않을까 싶었지만,
막상 구현해보니 예외 상황에 따라 흐름을 끊을지, 무시할지를 나누는 기준도 중요했습니다.
Redis 저장 실패 하나가 사용자 경험 전체를 망칠 수도 있고, 굳이 흐름을 끊지 않아도 되는 경우도 있어서
예외 처리에도 우선순위와 의도가 필요하다는 점을 학습했습니다.
'Dev Projects' 카테고리의 다른 글
| [table-now] 예약자-사장님 1:1 실시간 채팅 기능 설계 및 구현 (2) | 2025.06.04 |
|---|---|
| [table-now] Spring Security의 oauth2Login() 대신 WebClient를 선택한 이유 (3) | 2025.05.28 |
| [table-now] S3 이미지 업로드 - Presigned Url 방식으로 구현하기 (1) | 2025.05.23 |
| [table-now] AccessToken 블랙리스트 기반 토큰 무효화 기능 구현 (0) | 2025.04.22 |
| [table-now] Refresh Token 저장소를 Redis로 교체하며 인증 구조 고도화하기 (0) | 2025.04.21 |