Spring Boot 기반 서비스에서 OAuth2 인증을 적용하다 보면, 어떤 API는 잘 동작하는데 특정 API에서만 401 Unauthorized 오류가 발생하는 경우가 있습니다. 이번 글에서는 실제 문제 해결 과정을 바탕으로, 원인 파악과 수정 방법을 정리해 보겠습니다.
문제 상황
- 여러 개의 API 서비스를 운영하며 공통 인증을 적용했습니다.
- 일부 API는 정상 응답하지만, 특정 API 호출 시 401 Unauthorized 오류가 발생했습니다.
- 서버 로그에는 별다른 오류가 없어 원인 추적이 쉽지 않았습니다.
원인 후보
처음에는 다양한 가능성을 의심했습니다.
- 보안 필터 체인 매칭 문제: 특정 경로가 올바른 SecurityFilterChain을 타지 않는 경우
- 게이트웨이/프록시 문제: Authorization 헤더가 일부 경로에서 전달되지 않는 경우
- 쿠키 기반 인증 문제: 쿠키 설정(SameSite/도메인)으로 인해 토큰이 전달되지 않는 경우
- CORS 문제: OPTIONS 요청이 차단되어 본 요청까지 가지 못하는 경우
- 토큰 파싱 문제: Authorization 헤더에서 “Bearer ” 접두어를 제거하지 않아 인증 실패가 발생하는 경우
실제 코드 점검
기존 커스텀 토큰 추출기
@Component
public class CustomBearerTokenExtractor implements TokenExtractor {
@Override
public Authentication extract(HttpServletRequest request) {
String tokenValue = request.getHeader("Authorization");
if (StringUtils.isEmpty(tokenValue)) {
Cookie token = WebUtils.getCookie(request, "LOGIN_TOKEN");
if (token != null) {
tokenValue = token.getValue();
}
}
return new PreAuthenticatedAuthenticationToken(tokenValue, "N/A");
}
}
문제점
Authorization: Bearer <토큰>
전체 문자열을 그대로 사용 → “Bearer ” 접두어 제거 누락- 토큰이 없더라도 Authentication 객체를 생성 → 불필요한 예외 및 401 유발
해결 방법
수정된 커스텀 토큰 추출기
@Component
public class CustomBearerTokenExtractor implements TokenExtractor {
private static final String BEARER_PREFIX = "Bearer ";
@Override
public Authentication extract(HttpServletRequest request) {
String token = resolveFromHeader(request);
if (token == null) token = resolveFromCookie(request, "LOGIN_TOKEN");
if (token == null) return null; // 토큰 없으면 null 반환
return new PreAuthenticatedAuthenticationToken(token, "N/A");
}
private String resolveFromHeader(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith(BEARER_PREFIX)) {
return header.substring(BEARER_PREFIX.length()).trim();
}
return null;
}
private String resolveFromCookie(HttpServletRequest request, String name) {
Cookie c = WebUtils.getCookie(request, name);
return (c != null && !c.getValue().isBlank()) ? c.getValue().trim() : null;
}
}
개선 사항
Bearer
접두어를 제거하고 순수 토큰만 인증 모듈로 전달- 토큰이 없으면
null
반환 → Spring Security가 올바르게 401 처리
결과
- 프론트엔드에서 Authorization 헤더를 포함해 호출하면 인증이 정상적으로 처리됩니다.
- 401 Unauthorized 오류가 더 이상 발생하지 않았습니다.
- 인증 구조가 명확해지고 유지보수도 쉬워졌습니다.
배운 점
- 표준 포맷 준수: OAuth2 Bearer 토큰은 반드시 “Bearer ” 접두어를 제거하고 처리해야 합니다.
- null 반환 원칙: 토큰이 없을 때는 Authentication 객체를 억지로 만들지 않고 null을 반환해야 합니다.
- 환경 전반 점검 필요: 헤더 전달, 게이트웨이 설정, CORS, 쿠키 정책 등도 인증 실패의 주요 원인이 될 수 있습니다.
- 최신 방식 고려: Spring Security 최신 버전에서는
BearerTokenResolver
사용을 권장하므로 점진적 마이그레이션을 고려하는 것이 좋습니다.
결론
이번 문제는 단순히 “Bearer 접두어 제거 누락” 때문에 발생한 오류였습니다. 하지만 원인을 추적하는 과정에서 인증 체계를 전반적으로 점검하고 개선할 수 있었고, 결과적으로 더 안정적인 인증 환경을 마련할 수 있었습니다.
반응형
'개발 (Development) > Java' 카테고리의 다른 글
[Java/Spring Boot] "No thread-bound request found" 에러 원인과 해결법 (0) | 2025.09.19 |
---|---|
[Java/Spring Boot] JdbcTemplate으로 여러 데이터베이스 연결하기 (0) | 2025.09.14 |
[Java] JPA에서 DTO는 Interface로 구현할까? Class로 구현할까? (4) | 2025.08.03 |
[Java] Interface란? 클래스와 다른 점은? (0) | 2025.08.03 |
[Java] MyBatis와 JPA 속도 비교: 어떤 상황에 어떤 선택이 더 나을까? (0) | 2025.08.03 |