Spring

Spring DataSource, config server 에러 : Access denied for user

j1adev 2025. 10. 25. 14:21

 

 

1️⃣ “로컬 실행이 안된다”

입사 한 달 차, 로컬에서 테스트하려는데 앱이 뜨질 않았다.

Access denied for user ''@'123.456.789.10' (using password: NO)

username, password가 인식이 안되었다.
“비밀번호가 틀렸다”가 아니라,
“비밀번호를 보내지도 않았다”는 뜻이었다.

 


2️⃣ 문제 파악 : 로그를 찍자

  • application.yml에 username, password를 정확히 넣고,
    Config Server도 꺼봤지만 여전히 안 됐다.
    그래서 결국 Bean에 로그를 박았다.
@PostConstruct
public void debugDatasource() {
    HikariDataSource ds = (HikariDataSource) dataSource();
    System.out.println("==== MariaDB DataSource ====");
    System.out.println("URL: " + ds.getJdbcUrl());
    System.out.println("USER: " + ds.getUsername());
    System.out.println("================================");
}

 여전히 콘솔엔 username이 공백이었다.
심지어 jdbc-url을 바꿔도 계속 log4jdbc가 붙어서 찍혔다.

URL: jdbc:log4jdbc:mysql://123.456.789.10
USER:

 


3️⃣ 원인 분석 — “두 개의 함정”

이 문제에는 함정이 두 개 숨어 있었다.
하나는 코드 내부의 자동 설정 충돌,
다른 하나는 환경 설정의 공유 부재였다.

1. Spring Boot의 바인딩 충돌 (@ConfigurationProperties + DataSourceBuilder), 즉 local yml vs config server

처음엔 log4jdbc 문제인 줄 알았다.
하지만 원인은 Spring Boot의 자동 바인딩 순서였다.

나는 아래처럼 @ConfigurationProperties를 붙여  yml의 DB 설정을 읽게 했다.

 
@ConfigurationProperties(prefix = "spring.maria-db.datasource") 
public DataSource dataSource() {
	return DataSourceBuilder.create().type(HikariDataSource.class).build(); 
}

그런데 DataSourceBuilder.create()도 원격의config server의 같은 prefix(spring.maria-db.datasource)를 기반으로
username/password를 읽는다.

즉, 같은 prefix의 값이 중복 바인딩이 발생했다.

그 결과, DataSourceBuilder가 세팅한 username/password가
나중에 @ConfigurationProperties에 의해 null로 덮였다.

 
 

@ConfigurationProperties를 제거하고 강제로 DataSourceBuilder 빈에 usernmae, password설정해서 연결이 바로 성공했다.

 

2. DB 주소 변경 (팀 내 소통 부재)

두 번째 함정은 환경이었다.
누군가가 jdbc-url을 새롭게 바꿔 master에 머지했는데
이건 내가 권한이 없는 DB였다.
팀원 중 아무도 그 변경을 공유하지 않았고, 나는 권한도 없는 DB에 접속 시도만 하고 있었다.


코드가 맞아도, 소통이 되지 않으면 그게 버그다.

 


4️⃣ 해결 : @ConfigurationProperties 제거, 강제 설정

  • 애노테이션 제거, DataSource 빈도 수동 설정

예시:

@Bean
@Primary
public DataSource dataSource() {
    return DataSourceBuilder.create()
        .driverClassName("...")
        .url("jdbc:mysql:...")
        .username("...")
        .password("...")
        .build();
}

 


5️⃣ 회고 — 소통도 디버깅이다

사실 위의 문제는 대부분의 서비스회사는 마주치지 않을 가능성이 높다. 
@ConfigurationProperties 기반 수동 Bean 등록 + Config Server 방식은 sm 회사가 쓰는 오래된 기술이기 때문이다. 

  • 버전이 낮은 프레임워크, 구식 기술은 ‘문제 해결의 난이도’를 올린다. 그리고 그것보다 더 큰 문제는 소통 없는 환경이다.
  • Config나 DB 설정처럼 작은 변경일수록 문서화와 커뮤니케이션이 필수인데,
    SM 회사는 “각자 맡은 일만 하는 문화”가 강해서 소통이 잘안된다.

 

 

'Spring' 카테고리의 다른 글

Spring WebClient block() vs subscribe(): 계층별 사용법 정리  (1) 2025.06.18