본문 바로가기

Framework/Spring

SpringBoot + OAuth2 : 'Cannot convert access token to JSON', 그리고 예외에 대한 고찰

문제

  최근 개인 스터디 및 예제코드 보관용으로 코틀린 기반의 스프링 부트 어플리케이션을 구축하고 있다. 사실 이런 백엔드 시스템엔 인증이 빠질 수 없기에 Spring Security와 OAuth2를 연동하여 코드를 작성했는데, 인증방식은 보통 많이 쓰이는 'password grant' 방식을 사용했다.

 

 Access 토큰 발급 테스트까지 마치고, 해당 토큰을 Authorization 헤더에 추가해 특정 Endpoint 에 요청하니 서버로부터 다음과 같은 응답이 왔다.

{
    "error": "invalid_token",
    "error_description": "Cannot convert access token to JSON"
}

토큰은 JWT 방식으로 생성이 되는데, 인증에 사용하기 위해 프레임워크 레벨에서 JSON으로 변환이 된다.

위 메시지는 그 JSON 변환을 할 수 없다는 메시지다. (너무 당연한 영어)

 

분석

일단 내가 작성한 `AccessTokenConverter` 관련 설정코드 부터 살펴 봤다. 아래와 같다.

 

코드에 키 서명 파일 경로와 암호가 하드코딩 되어 있는건 그냥 넘어가자. (어디까지나 예제코드임)

 

`JwtTokenStore` 에 `JwtAccessTokenConverter` 까지 잘 세팅되어 있다. 문제가 없어 보이는데 도데체 왜 에러가 발생하는 것일까? 그 다음 내가 시도한건 직접 `JwtAccessTokenConverter` 클래스를 디버깅 하는 것이었다. 아래는 해당 클래스의 Jwt 토큰을 변환하는 메서드이다.

 

 

응? 왠 NPE? 실제 요청을 다시 디버깅하며 이 코드를 따라가 보니,

 

 

`SignatureVerifier` 가 null 값이었다. 해당  `Verifier` 는 `JwtAccessTokenConverter` 빈 생성시 호출하는 `setKeyPair` 메서드 내부 로직을 통해 토큰 컨버터 객체에 할당된다. 서두에 살펴봤던 설정 클래스에서도 해당 keyPair 할당 코드를 정상적으로 작성했기 때문에, 그에 따른 `SignatureVerifier` 또한 제대로 할당되어 있어야 했다. 이상했다. 다시 설정코드를 뚫어지게 쳐다봤다.

 

응..?

 그렇다. 애써 `Bean` 생성 메서드를 통해 토큰 컨버터를 만들어 놓고 정작 토큰 스토어에는 새로운 객체를 할당해 버린 것이다. `SignatureVerifier` 가 세팅되지 않은 객체, 즉 아무런 설정이 되어 있지 않은 객체로 JSON 변환을 시도하니 NPE가 날 수 밖에. 구차한 변명을 하자면, Kotlin의 객체 생성은 `new` 키워드가 생략되기 때문에 빈 생성 메서드 호출과 미처 구분하지 못했던 실수였다.

 

생각

 평소 예외(Exception)에 대한 나의 생각은, 명확한 오류 메시지를 전달하여 개발자로 하여금 트러블 슈팅을 손쉽게 해야한다는 것이다. 하지만 이번 경우는 그러한 좋은 예외 메시지의 조건을 갖추지 못함으로, 불가피하게 라이브러리 코드를 디버깅 해야하는 안좋은 사례라고 생각한다. 단지 'Cannot convert access token to JSON' 라는 메시지 만으로 그 복잡한 라이브러리 레벨의 오류를 어떻게 알 수 있단 말인가. 개발자는 비즈니스 로직을 구현하기도 바쁘다.

 

응? 아래처럼 예외를 다시 throw 할때 root cause 도 같이 전달해 주는데?

 

 

라고 말할 수 도 있다. 하지만 다시 코드를 쭉 따라가 보면,

 

 위 처럼 filter chain 을 거슬러 올라가면서 원래의 예외인 `NullPointerException` 은 흔적도 없이 사라져 버린다. 결론적으로,  `JwtAccessTokenConverter` 에서 실제 변환을 수행하는 `decode` 메서드에서 발생하는 모든 예외는 'Cannot convert access token to JSON' 이라는 메시지로 쳐진다는 소리다!

 

 사실 스프링 프레임워크로 개발을 하다 보면, `DispatcherServlet` 과 핸들러 메서드 사이에서 발생하는 라이브러리단 예외의 Root Cause가 저런식으로 소멸이 되는 바람에, 직접 `DispatcherServlet` 을 디버깅해야하는 경우가 있다. 딱 지금과 같은 상황에, 이것도 무시할 수 없는 비용(시간)이 드는 작업이다.

 

 사실 '라이브러리'라는 것은 범용으로 사용되는 코드이고, 이 때문에 '공통화' 에 초점을 맞춰 개발하다 보니 이렇게 예외 메시지 마저 공통화 시켜버린게 아닌가 생각이 들었다. 하지만, 다른건 몰라도 예외 메시지가 공통화 되는 것은 잘못 된 것이 아닌가? 뭐 내가 라이브러리 개발자가 아니니 그 이상 딴지를 걸 수는 없지만.

 

마무리

 소트프웨어 개발에서 몇 시간, 심하면 몇날 며칠을 삽질하며 발견해 낸 오류의 원인은 이처럼 매우 허망한 경우가 생각보다 많다. 원인이 허망할 지라도, 덕분에 라이브러리 코드에 대한 이해도를 높일 수 있었고, 예외에 대한 고찰과 평소 생각하고 있던 것에 대한 확신이 다시금 드는 경험 이었다. 예외는 문제 발생 지점의 명확한 원인이 개발자에게 전달되야 한다는 그 것 말이다.

 

 

 

 

'Framework > Spring' 카테고리의 다른 글

Kotlin + SpringBoot + Kafka 연동  (0) 2020.07.02