728x90
0. 개요
릴스에서 REST에 대한 개념적인 영상으로 다음과 같은 주제의 영상이 나왔고, 나는 댓글에 들어갔다.
당신의 API v1은 GET /getUserById?id=5 입니다.
v2는 GET /users/5 입니다.
당신의 테크 리드(개발 팀장)는 v1이 잘못되었다고 말합니다.
두 방식 모두 같은 데이터를 반환하고, 잘 작동합니다.
왜 URL 구조가 실제로 중요한 것일까요?
댓글
AAA: 둘 다 잘못되었습니다. 열거 공격(enumeration attacks)을 방지하려면 사용자를 조회할 때 항상 UUID를 사용해야 합니다.
BBB: 숫자 5를 실제 UUID나 CUID 등을 나타내기 위한 단순 예시(placeholder)로 볼 수도 있잖아요. 전 이게 그렇게 나쁘다고 생각하지 않아요.
AAA: 사람들에게 무엇이 "올바른" 방법인지 교육하려는 목적이라면 저런 예시는 여전히 나쁩니다.
영상의 API의 uri PK가 단순 정수형으로 나와있는 부분이 보안상 좋지않다는 부분에 대한 논쟁이였다. 이에 대해 알아보도록하겠다.
1.UUID(Universally Unique Identifier)란
컴퓨터 시스템에서 정보를 식별하기 위해 128비트 크기로 생성되는 고유한 식별자이다.
형식은 아래의 이미지와 같다.

주로 데이터베이스의 기본 키나 API 엔드포인트의 리소스 식별자로 활용된다.

2. 왜 쓰는가
2-1. 보안상 문제
여기 id가 1부터 순차적으로 1씩증가하는 PK를 가진 회원을 조회하는 API가 있다.

해당 부분의 문제성은 악의적인 사용자가 단순하게 id를 1씩증가시켜서 조회하는것만으로 다른 사용자들의 데이터가 조회되어 크롤링 될수 있다는것이다.

하지만 UUID를 사용하면 이런식으로 조회하게 되므로 위의 방식을 막을수 있다.
GET /api/members/123e4567-e89b-12d3-a456-426614174000
2-2. 분산 시스템(MSA)에서의 키 충돌 방지
UUID는 128비트로 구성되고 규칙을 나타내는 고정된 6비트를 제외한 122비트가 완전한 난수로 채워진다.
이는 UUID 의 경우의 수가 2^122 라는건데 RFC 4122에 따르면, 1조 개의 UUID를 생성해도 충돌 확률은 10억 분의 1이라고한다. 때문에 사실상 0%라고 간주한다.
트래픽을 감당하기 위해 DB를 여러 대로 나누면(Sharding), 각 DB가 독립적으로 숫자 1, 2, 3을 생성하므로 전체 시스템에서 고유한 ID를 보장할 수 없게 되는데, UUID는 데이터베이스에 의존하지 않고 애플리케이션 레벨에서 직접 생성하고 여러 대의 서버가 동시에 각자 UUID를 만들어도 전 세계적으로 값이 겹칠 확률이 0에 수렴하므로, 충돌 걱정 없이 안전하게 쓸 수 있다.

3. 실제 사용법(UUID v7)
3-1. java.util.UUID (v7 못사용)
기본 내장 클래스인 java.util.UUID를 사용하여 간단하게 생성할 수 있지만 UUID Version 4이다.
import java.util.UUID;
// UUID 객체 생성
UUID uuid = UUID.randomUUID();
// 문자열로 변환
String uuidString = uuid.toString();
3-2. SpringBoot 에서 사용(v7)
가장 널리 쓰이는 라이브러리는 uuid-creator이다.

Hibernate가 자동으로 ID를 생성하도록 맡기지 않고, JPA의 생명주기 콜백인 @PrePersist를 활용하여 DB에 저장되기 직전에 UUID v7을 직접 할당해 줍니다.
(타입자체는 Java 타입의 동일한 java.util.UUID입니다. 생성방식만 바뀝니다.)
@Entity
@Table(name = "orders")
public class Order {
@Id
// @GeneratedValue를 사용하지 않습니다.
@Column(columnDefinition = "BINARY(16)", updatable = false, nullable = false)
private UUID id;
@Column(nullable = false)
private String orderName;
// 엔티티가 DB에 INSERT 되기 직전에 실행됨
@PrePersist
protected void onCreate() {
if (this.id == null) {
// UUID v7 생성 및 할당
this.id = UuidCreator.getTimeOrderedEpoch();
}
}
// Getters
public UUID getId() { return id; }
public String getOrderName() { return orderName; }
}
4. UUID V4 vs UUID V7
4-1. UUID V4
철저하게 무작위로 생성되므로 값을 절대 예측할 수 없어 보안성이 매우 뛰어나지만 기본 키로 사용할 경우, 값이 뒤죽박죽으로 들어오기 때문에 B-Tree 인덱스가 계속 쪼개지고 섞이는 페이지 분할 및 단편화가 많이 발생한다.
API 토큰, 임시 파일명, 비밀번호 초기화 링크 등 절대 예측되면 안 되는 값에 활용하기에 좋다.
[난수] - [난수] - [버전+난수] - [변형+난수] - [난수]
4-2. UUID V7
앞부분이 시간순으로 점점 커지는 숫자이므로, 데이터베이스에 삽입될 때 기존 인덱스 트리 끝에 차곡차곡 쌓인다.
즉, Auto Increment와 유사한 우수한 INSERT 성능을 보여줍니다. 뒷부분은 난수라서 여전히 고유성과 어느 정도의 보안성을 보장한다.
데이터베이스 기본 키(PK), 생성 시간순 조회가 빈번한 식별자에서 사용하기에 좋다.
[유닉스 타임스탬프(48비트)] - [버전+난수] - [변형+난수] - [난수]
728x90
'BackEnd > SpringBoot' 카테고리의 다른 글
| vscode 자바 클래스들 경로 안잡힐때 해결법 (0) | 2026.04.28 |
|---|---|
| [SpringBoot] RabbitMQ (1) | 2026.01.09 |
| [SpringBoot] 통합테스트 (0) | 2025.11.23 |