[에러] javax.net.ssl.SSLException: closing inbound before receiving peer's close_notify

2022. 11. 22. 23:02BackEnd

 

아시는 분들은 다 아시는 너무 당연한 이야기를 기록삼아 한번 적어본다.

아마도 가끔 설정을 새로이 잡아주다가 보면 한번씩 마주치는 에러 같다. javax.net.ssl.SSLException인에 메시지는 closing inbound before receiving peer's close_notify 이다.

 

전체 내용은 아래와 같다. SpringBoot Application은 잘 동작하고 있는데, Socket과 관련된 Thread가 종료되고, Socket 연결이 끊어진것만 같은(?) 에러들이 발생했다. 결론 부터 이야기하면 SSL 옵션을 지정해주지 않았기 때문에 SQL을 실행하고 해당 DB Connection을 유지하지 않고 바로 종료하면서 발생시키는 에러라고 한다.

 

해결책은 SSL = false를 해주거나 SSL 인증에 필요한 정보를 함께 전달하는 것이다.

** BEGIN NESTED EXCEPTION ** 

javax.net.ssl.SSLException
MESSAGE: closing inbound before receiving peer's close_notify

STACKTRACE:

javax.net.ssl.SSLException: closing inbound before receiving peer's close_notify
	at java.base/sun.security.ssl.SSLSocketImpl.shutdownInput(SSLSocketImpl.java:757)
	at java.base/sun.security.ssl.SSLSocketImpl.shutdownInput(SSLSocketImpl.java:736)
	at com.mysql.cj.protocol.a.NativeProtocol.quit(NativeProtocol.java:1319)
	at com.mysql.cj.NativeSession.quit(NativeSession.java:182)
	at com.mysql.cj.jdbc.ConnectionImpl.realClose(ConnectionImpl.java:1750)
	at com.mysql.cj.jdbc.ConnectionImpl.close(ConnectionImpl.java:720)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy$LazyConnectionInvocationHandler.invoke(LazyConnectionDataSourceProxy.java:390)
	at com.sun.proxy.$Proxy66.close(Unknown Source)
	at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.closeConnection(DatasourceConnectionProviderImpl.java:127)
	at org.hibernate.internal.NonContextualJdbcConnectionAccess.releaseConnection(NonContextualJdbcConnectionAccess.java:46)
	at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.releaseConnection(LogicalConnectionManagedImpl.java:196)
	at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.close(LogicalConnectionManagedImpl.java:239)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.close(JdbcCoordinatorImpl.java:189)
	at org.hibernate.internal.AbstractSharedSessionContract.close(AbstractSharedSessionContract.java:327)
	at org.hibernate.internal.SessionImpl.closeWithoutOpenChecks(SessionImpl.java:431)
	at org.hibernate.internal.SessionImpl.close(SessionImpl.java:417)
	at org.springframework.orm.jpa.EntityManagerFactoryUtils.closeEntityManager(EntityManagerFactoryUtils.java:426)
	at org.springframework.orm.jpa.JpaTransactionManager.doCleanupAfterCompletion(JpaTransactionManager.java:620)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java:1007)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:793)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:138)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
	at com.sun.proxy.$Proxy78.findById(Unknown Source)
	at com.kingcjy.replication.service.ProductService.getProduct(ProductService.java:33)
	at com.kingcjy.replication.service.ProductService.test(ProductService.java:27)
	at com.kingcjy.replication.service.ProductService$$FastClassBySpringCGLIB$$2bd99f01.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
	at com.kingcjy.replication.service.ProductService$$EnhancerBySpringCGLIB$$2837504f.test(<generated>)
	at com.kingcjy.replication.controller.ProductController.test(ProductController.java:33)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:834)


** END NESTED EXCEPTION **

 

해결책 1

 

가장 간단한 해결책은 SSL을 사용하지 않는 것이다. 아래 예시처럼 application.yml이나 application.properties에 아래와 같이 useSSL=false를 추가해주면 된다.

spring.datasource.url=jdbc:mysql://localhost:3306?&useSSL=false

 

근데 이게 만들어져 있다는 것은 SSL 사용 목적이 있다는 것인데, 안쓰고 넘어간다고? 뭔가 이상하다.

 

 

SSL이 뭔데?

SSL은 Secure Sockets Layer의 약자로 암호화 기반의 인터넷 보안 프로토콜이라고 한다. Netscape사에서 1995년 개발한 것으로 TLS 암호화의 전신이라고 한다. 흔히 HTTPS를 사용하는 웹사이트에서 사용한다고 보면되겠다. 

 

동작방식은 간단하다. SSL 인증서를 함께 포함시켜 연결을 함으로서 높은 수준의 개인정보 보호를 제공한다.

 

 

이걸 왜 DB 연결에 필요한거지?

위에서 HTTPS를 사용하는 웹사이트 이야기를 했다고 웹사이트만 해당한다고 생각하면 안된다. (아마도 이 글을 읽는 독자들은 아주 똑똑하기에 그렇지는 않겠지만, 나는 웹에서 쓰는구나 이러고 넘어간 기억에...) 네트워크상에 노출되어 보안상 취약이 예상되는 네트워크 통신에 대해서 적용할 수 있는 방식이 SSL이다.

 

보통 Public Cloud 환경을 사용하는 등의 네트워크 환경에서는 외부의 공격에 의해 DB에 저장된 주요 정보가 외부에 유출될 수 있다.  그렇기에 보안이 필요하다.

 

 

모든 DB에 다 지원이 되는가?

이건 DB마다 다른것 같다. 버전별, 엔진별 차이는 직접 확인이 필요하고, 개인적으로 MySQL을 주로 쓰기에 해당 정보만 간략하게 남긴다.

아래 내용은 공식사이트 발췌 내용이다. (링크)

  • -DWITH_SSL={ssl_type|path_name}
    • ssl_type can be one of the following values:
      • yes: Use the system OpenSSL library if present, else the library bundled with the distribution.
      • bundled: Use the SSL library bundled with the distribution. This is the default prior to MySQL 5.7.28. As of 5.7.28, this is no longer a permitted value and the default is system.
      • system: Use the system OpenSSL library. This is the default as of MySQL 5.7.28.
    • path_name is the path name to the OpenSSL installation to use. This can be preferable to using the ssl_type value of system because it can prevent CMake from detecting and using an older or incorrect OpenSSL version installed on the system. (Another permitted way to do the same thing is to set WITH_SSL to system and set the CMAKE_PREFIX_PATH option to path_name.)
    For additional information about configuring the SSL library, see Section 2.9.6, “Configuring SSL Library Support”.
  • For support of encrypted connections, entropy for random number generation, and other encryption-related operations, MySQL must be built using an SSL library. This option specifies which SSL library to use.

결과적으로 위 내용을 돌이켜보면, SSL 기능을 사용하여 Connection이 가능하다는 것이다.

 

 

해결책 2

SSL 인증서를 포함한 형태로 DB에 Connection을 한다.

방법은 일단 링크만 남긴다.

 

 

결론

SSL은 보안 프로토콜이며 인터넷 네트워크에서 사용되는 방식이다.

SpringBoot Project와 MySQL간 DB Connection 시 SSL 사용 옵션을 사용할 수 있으며

개발과 테스트 환경에서 나는 SSL = false로 사용할 예정이다.