프로젝트의 배포환경을 구성하고 발생한 오류입니다. 로그인 후 JWT를 이용해 인증된 사용자의 접근만 허용하는 로직을 호출했을 때 다음과 같은 로그를 남기며 500 에러가 발생했습니다. 서버 단부터, 배포 단계까지 점검하면서 겨우 문제를 해결할 수 있었습니다. 굉장히 사소한 부분에서 발생한 문제였기 때문에 이번 같은 실수를 반복하지 않고자 해결 과정을 기록하려고 합니다.
구현한 인증, 인가 기능 및 배포 환경
- 인증, 인가: Spring boot, Spring Security + JWT
- 배포: AWS route53, cloudFront, EC2
저는 사용자의 인증, 인가를 Spring Security와 JWT로 구현했습니다. 로그인을 하면 사용자의 정보는 SecurityContext의 Authentication 객체 형태로 저장되고, 이렇게 저장된 객체 정보를 통해 현재 접근하는 사용자의 정보를 활용할 수 있습니다. 이외에도 securityFilterChain을 구성하고, CORS 설정을 추가해 인증, 인가 기능을 완성했습니다.
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
...
@GetMapping("/get")
public ResponseEntity<Dto> getApi(Authentication authentication){
.....
.....
}
서버 배포는 AWS의 route53, cloudFront, EC2를 이용했습니다. 호스팅 한 정적 웹 페이지에서 서버로 동적 리소스를 요청하면, route53을 거쳐 cloudFront를 통해 EC2로 호스팅 되도록 설정했습니다.
문제 상황
- 요청 헤더에 사용자의 인증, 인가 처리를 진행할 수 있는 JWT 토큰 값이 모두 담겼음에도 500 에러가 발생했습니다.
예상되는 원인
구현한 내용을 바탕으로 예상할 수 있는 문제는 다음과 같습니다.
- SecurityFilterChain 오류
- JWT access token 처리 필터 오류
- Authentication 주입의 실패
- 네트워크 문제
SecurityFilterChain 오류?
우선 Security filter chain의 순서를 확인했습니다. 주목할 부분은 커스텀 필터인 JwtAuthenticationFiilter와 JwtVerificationFilter, 그리고 AnonymousAuthenticationFilter입니다. 현재 로그인 한 사용자임에도 익명 사용자로 인식되는 것이 문제의 핵심이었고, 해당 내용은 AnonymousAuthenticationFilter에서 처리되고 있었습니다.
Spring Security의 처리 과정
Security filter chain: [
DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextHolderFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
JwtAuthenticationFilter
JwtVerificationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
AuthorizationFilter
]
AnonymousAuthenticationFilter란?
익명 사용자 정보를 SecurityContext에 저장하는 것을 목적으로 동작하는 필터입니다. 해당 필터를 통해 익명 사용자임을 명시할 수 있습니다. JwtAuthenticationFiilter와 JwtVerificationFilter 에서 사용자의 정보를 인식하지 못하면 해당 필터에 의해 요청을 보낸 사용자는 익명으로 명시됩니다.
저는 JwtAuthenticationFiilter와 JwtVerificationFilter에서 오류가 발생하는지 검증했습니다. 하지만 JWT를 검증하는 로직과, SecurityFilterChain에 해당 내용을 연결하는 과정을 모두 점검했지만 문제가 발생하는 지점은 알 수 없었습니다. 이 과정으로 SecurityFilterChain의 순서와, JWT Access Token 을 검증하는 로직에 이상이 없음을 확인했습니다.
Authentication 주입의 실패?
Filter에서 문제가 없었다면, Controller로 전달되는 Authentication 객체에서 문제가 있었던 걸까요? 하지만 클라이언트 측에서 Bearer 방식의 access token과 refresh token을 헤더에 온전히 담은 것을 확인했고, 토큰을 디코딩한 결과 현재 요청하는 사용자와 일치하는 것을 확인했습니다. Authentication 객체와 API 전달된 내용은 문제가 없었습니다.
해결
문제는 배포환경에서 발생했습니다. 서버의 로그를 처음부터 다시한번 확인한 결과, 헤더에 Access 토큰값을 의미하는 "Authorization"이 누락된 것을 확인했습니다. 애초에 서버로 인증을 위한 정보가 안 넘어왔기 때문에 서버는 해당 사용자를 익명으로 명시했던 것이었습니다.😂
HTTPS 환경을 구성하면서 사용한 cloudFront에서 헤더 값이 누락된 원인을 찾은 결과, 캐시 정책을 설정하면서 헤더 설정이 '없음'으로 설정된 것을 확인했고, 헤더들을 '모두' 원본에 요청할 수 있도록 수정했습니다.
너무 사소한 문제 때문에 많은 시간을 소모한 것 같습니다. AWS에 대한 공부가 더 필요한 것 같네요...🤦♂️
읽어주셔서 감사합니다!!
참고
https://repost.aws/ko/knowledge-center/cloudfront-authorization-header
Authorization 헤더를 오리진에 전달하도록 CloudFront 구성
Amazon CloudFront 배포의 오리진에는 요청에 Authorization 헤더가 포함되어야 합니다. 이 때문에 배포는 Authorization 헤더를 오리진으로 전달해야 합니다.
repost.aws