티스토리 뷰
[Spring Boot / 스프링 부트] toString()을 왜 오버라이딩해야 하는가 - 보안 로깅 관점
poopooreum 2026. 3. 28. 03:53✏️ toString()이란?
toString()은 java.lang.Object 클래스에 정의된 메서드다. Java의 모든 클래스는 Object를 암묵적으로 상속받으므로, 별도로 선언하지 않아도 모든 객체는 toString()을 가진다. Object의 기본 구현은 다음과 같다.

// java.lang.Object
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
```
// 오버라이딩하지 않으면 아래처럼 출력된다.
// 클래스명 + @ + 16진수 해시코드. 디버깅에도, 로깅에도 전혀 쓸모없는 값이다.
com.example.OAuthNaverProperties@7852e922
✏️ toString()이 자동으로 호출되는 시점
명시적으로 .toString()을 호출하지 않아도 Java 컴파일러가 내부적으로 자동 호출하는 상황이 여럿 있다. 이 점을 모르면 의도치 않은 정보 노출로 이어진다.
① 문자열 연결(+) 연산
Java 컴파일러는 + 연산에서 객체가 피연산자로 오면 내부적으로 String.valueOf(obj)를 호출하고, 이는 다시 obj.toString()을 호출한다.
OAuthNaverProperties props = new OAuthNaverProperties(...);
String msg = "설정값: " + props; // props.toString() 자동 호출
② System.out.println()
System.out.println(props); // PrintStream.println(Object)이 내부적으로 toString() 호출
③ 로거(Logger) — 가장 위험한 케이스
clientSecret이 그대로 노출된다. 이 로그가 외부 로그 수집 시스템(ELK, CloudWatch 등)으로 전송되면 보안 사고로 이어질 수 있다.
log.info("설정값: {}", props); // SLF4J가 내부적으로 toString() 호출
log.info("설정값: " + props); // 문자열 연결 시점에 toString() 호출
```
운영 서버 로그에 아래처럼 찍힐 수 있다.
```
INFO - 설정값: OAuthNaverProperties[clientId=p4hAMwtBzH, clientSecret=b2KnBXZND_, redirectUri=http://...]
④ String.valueOf() / String.format()
String s = String.valueOf(props); // toString() 호출
String f = String.format("%s", props); // toString() 호출
✏️ class vs record — 왜 record가 더 위험한가
일반 class는 toString()을 자동 생성하지 않는다. 오버라이딩하지 않으면 Object의 기본 구현(해시코드)이 그대로 사용되므로, 의도치 않은 필드 노출은 없다.
반면 Java 16에서 정식 도입된 record는 컴파일러가 모든 필드를 포함한 toString()을 자동으로 생성한다.

public record OAuthNaverProperties(
String clientId,
String clientSecret,
String redirectUri,
String authBaseUrl,
String apiBaseUrl
) {}
위 record의 자동 생성 toString()은 다음과 동일하다.
@Override
public String toString() {
return "OAuthNaverProperties[" +
"clientId=" + clientId +
", clientSecret=" + clientSecret + // 그대로 노출
", redirectUri=" + redirectUri +
", authBaseUrl=" + authBaseUrl +
", apiBaseUrl=" + apiBaseUrl +
']';
}
record는 불변 데이터 객체를 간결하게 표현하기 위한 문법이라 설정 값, DTO, 응답 객체에 자주 쓰인다. 민감한 값을 담는 경우가 많은데 toString()이 자동 생성된다는 점에서 특히 주의가 필요하다.
✏️ 해결책 — toString() 오버라이딩으로 민감 필드 마스킹
@ConfigurationProperties(prefix = "oauth.naver")
public record OAuthNaverProperties(
String clientId,
String clientSecret,
String redirectUri,
String authBaseUrl,
String apiBaseUrl
) {
@Override
public String toString() {
return "OAuthNaverProperties[" +
"clientId=" + clientId +
", clientSecret=***" + // ← 마스킹
", redirectUri=" + redirectUri +
", authBaseUrl=" + authBaseUrl +
", apiBaseUrl=" + apiBaseUrl +
']';
}
}
```
이제 어디서 로깅하더라도 아래처럼 출력된다.
```
INFO - 설정값: OAuthNaverProperties[clientId=p4hAMwtBzH, clientSecret=***, redirectUri=http://...]
✏️ 사용자 정보 record도 동일하게 처리
API 키나 시크릿만의 문제가 아니다. 사용자 개인식별정보(PII, Personally Identifiable Information)도 동일하게 마스킹해야 한다. nickname을 노출하는 이유는 디버깅 시 어떤 사용자인지 식별하는 데 도움이 되면서도, 그 자체로는 개인을 특정하기 어렵기 때문이다. 이처럼 마스킹 기준도 필드별로 판단해야 한다.
public record OAuthUserInfo(
OAuthProvider oauthProvider,
String oauthId, // 외부 서비스 식별자
String email, // 개인정보
String nickname
) {
@Override
public String toString() {
return "OAuthUserInfo[" +
"oauthProvider=" + oauthProvider +
", oauthId=***" + // ← PII 마스킹
", email=***" + // ← PII 마스킹
", nickname=" + nickname +
']';
}
}
✏️ toString() 오버라이딩 방법 비교
① 직접 구현 (record에 권장)
가장 명시적이고 제어가 쉽다. record에는 이 방법을 사용한다.
@Override
public String toString() {
return "OAuthNaverProperties[clientId=" + clientId +
", clientSecret=***]";
}
② Lombok @ToString (class에 권장)
exclude에 지정한 필드는 toString() 출력에서 완전히 빠진다. 단, record에는 Lombok을 적용하기 까다로우므로 직접 구현이 더 적합하다.
@ToString(exclude = {"clientSecret", "password"})
public class SomeConfig {
private String clientId;
private String clientSecret; // 자동 생성 toString()에서 제외
private String password; // 자동 생성 toString()에서 제외
}
③ @ToString.Exclude (필드 레벨)
@ToString(exclude)와 @ToString.Exclude에 차이점은 해당 게시글을 참고하길 바란다.
https://pooreumjung.tistory.com/568
[Java/자바] - @ToString(exclude) vs @ToString.Exclude
✏️ 들어가기 전에 — Lombok의 @ToString이란?Lombok의 @ToString을 클래스에 붙이면 컴파일 시점에 모든 필드를 포함한 toString() 메서드를 자동으로 생성해준다.@ToStringpublic class User { private Long id; private
pooreumjung.tistory.com
@ToString
public class User {
private String name;
@ToString.Exclude
private String password; // 이 필드만 제외
}
✏️ 순환 참조 — JPA 엔티티에서 자주 발생하는 문제
toString() 오버라이딩 시 또 하나 주의할 점은 순환 참조다. JPA에서 양방향 연관관계를 맺은 엔티티에서 흔히 발생한다.
@Entity
public class Order {
private User user;
@Override
public String toString() {
return "Order{user=" + user + "}"; // user.toString() 호출
}
}
@Entity
public class User {
private List<Order> orders;
@Override
public String toString() {
return "User{orders=" + orders + "}"; // 각 Order의 toString() 호출 → 무한 루프!
}
}
Order.toString() → User.toString() → Order.toString() → ... 가 반복되어 StackOverflowError가 발생한다.
해결책: 연관 객체는 ID만 출력하거나 아예 제외한다.
@Override
public String toString() {
return "Order{id=" + id +
", userId=" + (user != null ? user.getId() : null) + // ID만 출력
'}';
}
✏️ 마스킹이 필요한 필드 기준
| 인증 시크릿 | clientSecret, apiKey | ✅ 필수 |
| 비밀번호 | password, hashedPassword | ✅ 필수 |
| 토큰 | accessToken, refreshToken | ✅ 필수 |
| 개인 식별자 | oauthId, socialId | ✅ 권장 |
| 개인정보(PII) | email, phone, birth | ✅ 권장 |
| 공개 식별자 | nickname, userId | ⬜ 상황에 따라 |
| 설정 URL | redirectUri, baseUrl | ⬜ 상황에 따라 |
✏️ 정리
- toString()은 Java 모든 객체가 가진 메서드로, 문자열 연결·로거·println 등에서 자동 호출된다.
- class는 기본 구현이 해시코드라 필드 노출이 없지만, record는 컴파일러가 모든 필드를 포함한 toString()을 자동 생성하므로 민감한 필드가 있다면 반드시 오버라이딩해야 한다.
- 오버라이딩 방법은 record라면 직접 구현, 일반 class라면 Lombok @ToString(exclude = {...})이 권장된다.
- JPA 엔티티의 양방향 연관관계에서는 toString()으로 인한 순환 참조와 StackOverflowError를 주의해야 한다.
toString()은 단순해 보이지만, 로깅 보안과 디버깅 편의성 모두에 직결되는 메서드다. 특히 운영 환경에서 민감한 값이 로그에 남는 사고는 코드 한 줄로 예방할 수 있다.
'Back-End > 개인 공부' 카테고리의 다른 글
| Nginx + SSL + DNS (0) | 2026.04.30 |
|---|---|
| 무중단 배포 방식 정리 - Rolling, Blue-Green, Canary (0) | 2026.03.30 |
| [Spring Boot / 스프링 부트] 카카오 & 네이버 소셜 로그인 구현하기 (OAuth 2.0) (0) | 2026.03.28 |
| [AWS] - AWS CodeDeploy와 Blue-Green 배포로 무중단 배포 환경 구축하기 (0) | 2026.03.23 |
| [AWS] - SSH 터널링 구성과 AWS Subnet 구조 (0) | 2026.03.09 |
- Total
- Today
- Yesterday
- c++ string
- 이분 매칭
- C++ Stack
- DFS
- 카운팅 정렬
- C++
- 알고리즘
- BFS
- js
- html
- 세그먼트 트리
- 유클리드 호제법
- 자바스크립트
- 스프링 부트 crud 게시판 구현
- CSS
- 알고리즘 공부
- 백준
- 자바
- 우선순위 큐
- Do it!
- 스택
- 자료구조
- 반복문
- DP
- 유니온 파인드
- HTML5
- 백준 풀이
- 투 포인터
- 에라토스테네스의 체
- java
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
