1. 문제 상황
현재 스프링부트 프로젝트를 run 했을 때,
com.mysql.cj.exceptions.UnableToConnectException: Public Key Retrieval is not allowed 보안 오류가 발생하는 상황이다.
사용중인 application.yml은 다음과 같다.
spring:
datasource:
url: jdbc:mysql://localhost:3306/[database_name]?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username:
password:
driver-class-name: com.mysql.cj.jdbc.Driver
2. 문제 원인
이 에러는 MySQL 8.x부터 기본 인증 플러그인이 mysql_native_password에서 caching_sha2_password로 변경되었는데,
이로 인해 JDBC 드라이버가 공개키를 요청하는 과정에서 문제가 발생한다.
public key retrieval이란?
- MySQL 서버가 caching_sha2_password 인증 방식을 사용할 경우, 클라이언트는 비밀번호를 암호화해야 전송할 수 있다.
- 그런데 암호화를 하려면 서버의 공개키(public key) 가 필요하다.
- allowPublicKeyRetrieval=true 옵션을 켜면, 클라이언트(JDBC 드라이버)가 서버로부터 공개키를 받아올 수 있게 된다.
즉, 비밀번호를 보호하기 위해 서버에서 공개키를 내려받는 과정이 허용된다는 뜻이다.
그러나 이 상태에서 JDBC URL에 allowPublicKeyRetrieval=true만 추가해서는 보안적으로 안전하지 않다.
왜냐하면 기존 JDBC URL에 있던 useSSL=false 때문이다.
useSSL이란?
- useSSL은 공개키 전달 과정을 평문(암호화 안된 상태)으로 전달할지 여부를 결정하는 요소이다.
- useSSL=false 라면 SSL/TLS 연결 자체를 사용하지 않는다. 따라서 이 공개키 전달 과정이 평문으로 네트워크를 통해 흘러간다.
- 기존 hs256 대칭키 암호화 방식에서는 useSSL=false를 사용해도 문제가 없었던 이유는, mysql_native_password 방식에서는 다음과 같은 과정으로 비밀번호가 전달된다.
- 서버가 랜덤 nonce(랜덤 문자열, salt) 를 클라이언트에 전달.
- 클라이언트는 비밀번호를 평문으로 보내는 게 아니라 SHA1(password + salt) 같은 단방향 해시 처리된 값을 보냄.
- 서버는 저장된 해시값과 비교해서 인증.
즉, 비밀번호 자체는 평문으로 전달되지 않기 때문에 SSL을 적용안해도 문제가 발생하진 않았었다.
- 하지만, public key retrieval을 통해 공개키 암호화 방식을 사용하는 경우 공격자가 네트워크를 스니핑(패킷 가로채기)하거나 MITM(중간자 공격)을 시도하면:
- 서버의 공개키를 위조해서 내려줄 수 있음.
- 클라이언트가 그걸 진짜라고 믿고 암호화 → 비밀번호를 유출할 위험이 생김.
- 따라서 allowPublicKeyRetrieval=true 옵션을 사용하는 경우에는 useSSL=true 로 함께 켜줘야 보안적으로 안전하다.
3. 해결 방법
spring:
datasource:
url: jdbc:mysql://localhost:3306/[database_name]?useSSL=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username:
password:
driver-class-name: com.mysql.cj.jdbc.Driver
JDBC URL에 allowPublicKeyRetrieval=true, useSSL=true 두 옵션을 설정해줌으로써 문제를 해결할 수 있다.