[Spring] - SSLHandshakeException

🛠️ 개발 환경 및 테스트 환경

  • OS : Window / Mac
  • kotlin : 2.0.21
  • Spring Boot : 3.3.0

💬 상황 설명

회사 프로젝트는 로컬 스프링 서버에서도 SSL 인증서를 사용해 https로 통신을 하며, 프론트는 노드 서버로 동작하여 백엔드 서버와 다른 포트로 동작한다. 그 때문인지 서버에 통신을 할 때, 다음과 같은 에러가 발생했다.

2024-10-18T09:53:10.185+09:00 WARN 10384 --- [App] [ctor-http-nio-2] .s.ApplicationProtocolNegotiationHandler : [id: 0x8fa98143, L:/[0:0:0:0:0:0:0:1]:8080 - R:/[0:0:0:0:0:0:0:1]:56868] Failed to select the application-level protocol: 
    javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown 
    java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130) ~[na:na] 
    java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117) ~[na:na] 
    java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:365) ~[na:na] 
    java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:287) ~[na:na] 
    java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:204) ~[na:na] 
    java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172) ~[na:na] 
    java.base/sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:736) ~[na:na]
    java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:691) ~[na:na]
    java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:506) ~[na:na]
    java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:482) ~[na:na]

로그를 확인해보니 SSLHandshakeException 에러임을 알 수 있었다.

✅ 해결 과정

1. 요구 분석

우선 SSLHandshake가 어떻게 진행되는지 알아보았다.

출처 : https://velog.io/@dewgang/SSL-Handshake

사진을 보면 알 수 있듯이, 브라우저에서 스프링 서버에 요청을 한 뒤, 서버는 SSL 인증서를 전달하게 된다. 이 과정에서 브라우저는 서버가 보낸 SSL 인증서를 신뢰할 수 없어, 해당 에러가 발생한 것이다.

즉, 이름 그대로 SSL을 이용해 브라우저와 웹 서버간 악수를 해야하는데, 브라우저가 웹 서버를 믿지 못해서 악수를 거부한 것이다.

대부분 로컬 환경에서 사용하는 SSL 인증서는 자가 서명된 인증서이므로, 브라우저는 더더욱 신뢰할 수 없을 것이다.

극단적인 예로, 본인 이름이 안성재인 평범한 직장인이 있다. 트리드를 너무 예약하고 싶어서 매장에 전화해 자기가 미슐랭 3스타 안성재니까 예약해달라고 말하는 것과 비슷하다.

2. 해결 방법

가장 간단한 방법은 https:localhost:8080에 접속하여 해당 브라우저를 신뢰하도록 하는 것이다. 1번에서 확인한 것과 같이 브라우저는 서버에서 보낸 SSL 인증서를 신뢰하지 않는다. 때문에, 해당 URL에 접속하면, 안전하지 않은 페이지라는 문구와 함께 다음과 같은 화면이 나오게 된다.

여기서 고급 버튼을 누르면, localhost(안전하지 않음) (으)로 이동이라는 문구가 나오게 되고, 해당 버튼을 클릭하면, 프론트와 서버가 정상적으로 통신할 수 있게 된다.

 

만약 이게 귀찮다면, 크롬 브라우저에 인증서 설치를 해두면, 해당 인증서가 수동으로 신뢰 목록에 추가 되어 매 번 저 페이지를 방문하지 않아도 되게 된다. (단, PEM || CRT || CER 파일이 있어야 함)

🤔 회고

한 번 인증서를 등록해두면 꽤 오래동안 재인증하지 않아도 되지만, 인증서 보관 기간이 만료되면 해당 페이지에 다시 접근하여 인증서를 신뢰하도록 다시 인증해주어야하는 과정이 많이 귀찮게 느껴진다. 서버에서 처리할 수 있다면 다양한 방법이 있겠지만, 자가 인증은 어쩔 수 없는 것 같다.