---
term: 'BaaS 보안 강화: CLP, ACL, 마스터 키 보호'
seoTitle: 'BaaS 보안 강화란? CLP, ACL, 마스터 키 보호'
headline: 'BaaS 보안 강화란?'
slug: baas-security-hardening
category: auth-security
shortDefinition: 'BaaS 보안 강화는 CLP, ACL, 키 관리 원칙으로 이루어진 계층형 통제로, 백엔드가 기본값으로 열어 둔 틈을 닫는 작업입니다.'
relatedTerms:
  - class-level-permissions-clp
  - access-control-lists-acl
  - api-key-security
  - data-layer-vs-application-layer-security
contrastsWith:
  - data-layer-vs-application-layer-security
faq:
  - question: 'BaaS 보안 강화란 무엇입니까?'
    answer: '허용적인 개발용 백엔드를 잠긴 프로덕션 백엔드로 바꾸는 출시 전 점검입니다. 클래스 수준 권한을 작업별로 제한하고, 사용자 데이터에 객체별 ACL을 적용하고, 마스터 키를 서버 코드 안에 가두고, 스키마 변경을 동결하고, 속도 제한을 설정하고, 실제 클라이언트에서 설정을 감사합니다. 각 통제는 서로 다른 계층을 담당하며, 모든 요청에서 이 계층들이 순서대로 검사됩니다.'
  - question: 'Application ID는 시크릿입니까?'
    answer: '아닙니다. 공개된 것으로 취급해야 합니다. 클라이언트 키와 앱 ID는 모든 모바일 바이너리와 모든 JavaScript 페이지에 실려 배포되며, 누구든 이를 추출할 수 있습니다. 이들은 앱을 식별할 뿐 호출자를 인증하지 않습니다. 실제 보호는 식별 이후 백엔드가 강제하는 것, 즉 클래스 수준 권한, ACL, 인증에서 나옵니다. 클라이언트 측 키가 모두 알려져도 버티는 계층들입니다.'
  - question: '왜 마스터 키를 클라이언트 앱에 절대 넣어서는 안 됩니까?'
    answer: '모든 관문을 우회하기 때문입니다. 마스터 키로 보낸 요청에는 클래스 수준 권한, ACL, 보호 필드, 인증 검사가 모두 적용되지 않습니다. 클라이언트 바이너리에 박힌 키는 앱을 내려받은 누구나 추출할 수 있으며, 그 순간 데이터베이스 전체가 공개 읽기/쓰기 상태가 됩니다. 마스터 키는 서버 측 코드에만 있어야 하며, 작업 단위로 사용하고 클라이언트가 닿을 수 있는 곳에는 절대 저장하지 않아야 합니다.'
  - question: 'CLP와 ACL의 차이는 무엇입니까?'
    answer: '범위입니다. 클래스 수준 권한은 스키마에 걸린 하나의 규칙으로 "누가 이 클래스에서 이 작업을 아예 실행할 수 있는가"에 답합니다. ACL은 각 객체에 담긴 데이터로 "누가 이 행을 건드릴 수 있는가"에 답합니다. 요청은 먼저 클래스 관문을, 그다음 객체 관문을 통과하며, 어느 쪽이든 거부할 수 있습니다. 보안 강화는 둘 다 사용합니다. 스키마에는 굵은 선을, 행에는 세밀한 결을 적용합니다.'
  - question: '유출된 마스터 키는 어떻게 회전합니까?'
    answer: '즉시 플랫폼 대시보드에서 새 키를 생성하고, 서버 측의 모든 사용처(Cloud Code, 작업, 관리 스크립트, CI)를 업데이트한 뒤 이전 키를 폐기합니다. 그런 다음 유출된 키가 유효했던 동안 무엇에 접근했을 수 있는지 감사합니다. 회전은 장애 대응만이 아니라 일상 업무여야 합니다. 키는 시간이 지나면서 로그, 백업, 오래된 노트북으로 흘러 들어가므로, 인증서 갱신처럼 회전 일정을 잡아 두는 것이 좋습니다.'
  - question: '쓰기는 언제 SDK 대신 Cloud Code를 거쳐야 합니까?'
    answer: '규칙에 신원만이 아니라 로직이 필요할 때입니다. 여러 객체에 걸친 불변 조건, 가격 책정, 재고, 돈이나 할당량이 관련된 모든 것이 해당합니다. 보안을 강화한 패턴은 클라이언트가 직접 쓸 수 없도록 클래스를 잠그고, Cloud Code 함수를 유일한 출입구로 노출합니다. 검증, 데이터 보강, 감사 로깅이 함께 따라오며, 클래스 관문은 누구도 이 단일 통로를 우회할 수 없음을 보장합니다.'
  - question: '속도 제한도 BaaS 보안 설정에 포함됩니까?'
    answer: '그렇습니다. 권한은 누가 엔드포인트를 호출할 수 있는지를 정하고, 속도 제한은 얼마나 자주 호출할 수 있는지를 정합니다. 속도 제한이 없으면 유효한 자격 증명이 스크래핑이나 무차별 대입 도구가 됩니다. 로그인 엔드포인트는 크리덴셜 스터핑을 당하고, 열린 쿼리로 데이터가 회선 속도만큼 빠르게 대량 수집됩니다. 인증 경로와 비용이 큰 쿼리에는 더 엄격한 제한을 두고, 알림과 함께 구성해 비정상적인 트래픽 급증이 예상치 못한 청구서가 아니라 온콜 호출로 이어지게 해야 합니다.'
  - question: 'BaaS 보안 설정은 어떻게 감사합니까?'
    answer: '공격자처럼 외부에서 테스트합니다. 공개 클라이언트 키만 가지고 익명 사용자, 인증된 사용자, 다른 테넌트의 사용자로서 모든 클래스를 읽고 쓰기를 시도합니다. 성공해서는 안 되는데 성공한 것은 모두 발견 사항입니다. 스키마가 바뀔 때마다 반복하고, 권한 매트릭스를 버전 관리되는 문서로 유지하며, 존재해서는 안 되는 마스터 키 사용 패턴이 없는지 로그를 검토합니다.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'OWASP API Security Top 10'
    url: 'https://owasp.org/API-Security/'
  - name: 'OWASP Authorization Cheat Sheet'
    url: 'https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html'
  - name: 'Backend security guide'
    url: 'https://docs.parseplatform.org/parse-server/guide/#security'
  - name: 'Back4app app security guidelines'
    url: 'https://www.back4app.com/docs/security/parse-security'
cta:
  title: '보안 강화는 프로젝트가 아니라 체크리스트입니다'
  text: 'Back4app에서는 보안 강화 작업 전체가 하나의 대시보드에 있습니다. 체크박스로 설정하는 클래스 수준 권한, 모든 요청에서 강제되는 ACL, 서버 측에 보관되는 마스터 키, 그리고 특권 경로를 위한 Cloud Code까지. 오후 한나절이면 백엔드를 잠글 수 있습니다.'
  linkText: '무료로 시작하기'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-11-03'
translationKey: baas-security-hardening
---

**BaaS 보안 강화는 CLP, ACL, 키 관리 원칙으로 이루어진 계층형 통제로, 백엔드가 기본값으로 열어 둔 틈을 닫는 작업입니다.** 백엔드는 의도적으로 허용적인 상태로 태어납니다. 열린 클래스와 클라이언트가 쓸 수 있는 스키마 덕분에 프로토타입이 빠르게 굴러갑니다. 보안 강화(hardening)는 출시 전에 이 기본값을 뒤집는 의도적인 점검이며, 연구 프로젝트가 아니라 짧고 이미 알려진 체크리스트입니다.

## 핵심 요약

| 질문 | 답변 |
| --- | --- |
| 정의 | 클래스 권한, ACL, 키, 스키마를 잠그는 출시 전 점검 |
| 계층 | CLP가 클래스를 → ACL이 행을 → 보호 필드가 열을 통제 |
| 유일한 절대 원칙 | 마스터 키는 모든 것을 우회하므로 절대 서버를 떠나지 않음 |
| 특권 경로 | 민감한 쓰기는 감사 가능한 출입구인 Cloud Code 함수로 |
| 키에 대한 오해 | 앱 ID와 클라이언트 키는 시크릿이 아니라 식별자이므로 그에 맞게 설계 |

## 코드로 보는 보안 강화 기본값

객체 단위 보호에 해당하는 부분입니다. 잠긴 클래스 아래에 소유자 범위의 ACL을 겹쳐 둡니다.

**JavaScript:**

```javascript
// JavaScript / Node.js — Back4app JS SDK
// Per-object ACL: the owner reads and writes, a moderator role reads,
// the public gets nothing — layered under the class-level permissions
const note = new Parse.Object('Note');
note.set('body', 'quarterly numbers');

const acl = new Parse.ACL(Parse.User.current()); // owner: read + write
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(false);
acl.setRoleReadAccess('moderator', true);        // role: read only
note.setACL(acl);
await note.save();

// The class gate above it is schema config, set in the dashboard:
// find/get: requiresAuthentication · create: authenticated · addField: nobody
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK
// Per-object ACL: owner read/write, moderator role read, public nothing
final note = ParseObject('Note')..set('body', 'quarterly numbers');

final acl = ParseACL(owner: await ParseUser.currentUser()); // owner: r+w
acl.setPublicReadAccess(allowed: false);
acl.setPublicWriteAccess(allowed: false);
acl.setReadAccess(userId: 'role:moderator', allowed: true); // role: read
note.setACL(acl);
await note.save();

// The class gate above it is schema config, set in the dashboard:
// find/get requiresAuthentication, create authenticated, addField off
```

**Swift:**

```swift
// iOS / Swift — Back4app Swift SDK
// Per-object ACL: owner read/write, moderator role read, public nothing
var note = Note()
note.body = "quarterly numbers"

var acl = ParseACL()
acl.publicRead = false
acl.publicWrite = false
if let user = User.current {
    acl.setReadAccess(user: user, value: true)   // owner: read
    acl.setWriteAccess(user: user, value: true)  // owner: write
}
acl.setReadAccess(roleName: "moderator", value: true) // role: read only
note.ACL = acl
note.save { result in
    if case .failure(let error) = result { print(error) }
}
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK
// Per-object ACL: owner read/write, moderator role read, public nothing
val note = ParseObject("Note").apply { put("body", "quarterly numbers") }

val acl = ParseACL(ParseUser.getCurrentUser()) // owner: read + write
acl.publicReadAccess = false
acl.publicWriteAccess = false
acl.setRoleReadAccess("moderator", true)       // role: read only
note.acl = acl
note.saveInBackground { e ->
    if (e != null) Log.w("ACL", "save failed: ${e.code}")
}

// The class gate above it is schema config, set in the dashboard:
// find/get requiresAuthentication, create authenticated, addField off
```

## CLP vs. ACL vs. 마스터 키: 각 통제가 하는 일

| 통제 | 범위 | 설정 위치 | 실패 방식 |
| --- | --- | --- | --- |
| [클래스 수준 권한](/glossary/ko/class-level-permissions-clp/) | 클래스 전체, 작업별 | 스키마(대시보드) | 허용적인 기본값 그대로 방치 |
| [ACL](/glossary/ko/access-control-lists-acl/) | 객체 하나 | 각 행, 저장 시점 | 새 객체에서 누락 |
| 보호 필드 | 열 하나 | 스키마 | 민감한 열을 다른 사용자가 읽을 수 있음 |
| 마스터 키 | 위의 모든 것을 우회 | 서버 환경에만 | 클라이언트 바이너리에 포함되어 배포 |

앞의 세 가지는 모든 요청이 올라야 하는 권한의 사다리를 이룹니다. 네 번째는 그 사다리의 탈출구입니다. 서버 측에서는 없어서는 안 되지만 다른 곳에서는 재앙입니다. 그리고 앱이 실제로 싣고 배포하는 식별자, 즉 앱 ID와 클라이언트 [API 키](/glossary/ko/api-key-security/)는 완전히 다른 범주에 속합니다. 어떤 바이너리에서든 추출할 수 있으므로 앱을 보호하는 것이 아니라 식별할 뿐입니다. 공개된 것으로 가정하고, 강제는 데이터 계층에 맡겨야 합니다.

## 적대적인 요청이 멈추는 곳

```mermaid
flowchart LR
  accTitle: 보안을 강화한 BaaS 백엔드에서 적대적인 요청이 통과해야 하는 계층형 관문
  accDescr: 추출한 클라이언트 키를 가진 요청은 식별을 통과한 뒤 속도 제한, 클래스 수준 권한 관문, 객체별 ACL, 보호 필드 필터링을 차례로 만나며, 각 계층은 요청을 거부할 수 있습니다. 별도의 마스터 키 경로는 모든 관문을 우회하며, 그래서 마스터 키는 서버 측에 머물러야 합니다.
  A["추출한 클라이언트 키를<br/>가진 요청"] --> B["속도 제한"]
  B --> C{"CLP: 이 호출자가<br/>이 작업을 실행할 수 있는가?"}
  C -- 아니요 --> X1["거부"]
  C -- 예 --> D{"ACL: 이 호출자가<br/>이 객체에 접근할 수 있는가?"}
  D -- 아니요 --> X2["거부"]
  D -- 예 --> E["보호 필드<br/>제거"] --> F["데이터"]
  M["마스터 키<br/>(서버 전용)"] -. 모든 관문을 우회 .-> F
```

이 다이어그램은 그대로 감사 스크립트이기도 합니다. 각 관문에서, 앱의 공개 키를 가진 공격자가 익명 사용자로, 로그인한 사용자로, 다른 테넌트의 사용자로 도착하면 무슨 일이 일어나는지 물어야 합니다. 예상치 못한 "허용"이 나올 때마다 그것이 발견 사항입니다.

## BaaS 백엔드의 보안을 강화하는 방법

| 단계 | 조치 | 막는 것 |
| --- | --- | --- |
| 1 | 모든 클래스의 CLP 설정: 최소 `requiresAuthentication`, 역할 범위의 쓰기 | 고전적인 익명 전체 테이블 덤프 |
| 2 | 모든 클래스에서 `addField`와 클라이언트의 클래스 생성 끄기 | 스키마 드리프트와 불필요한 클래스 주입 |
| 3 | 사용자 소유 객체의 기본 ACL: 소유자 읽기/쓰기, 공개 권한 없음 | 사용자 간 읽기와 쓰기 |
| 4 | 민감한 열(이메일, 토큰, 점수)을 필드 수준 규칙으로 보호 | 다른 사용자가 비공개 속성을 읽는 것 |
| 5 | 마스터 키를 서버 환경에 가두고, 정기적으로 그리고 유출 시마다 회전 | 전면 우회 노출 |
| 6 | 민감한 쓰기는 잠긴 클래스를 뒤에 둔 Cloud Code 함수로 라우팅 | 비즈니스 규칙 위반과 변조 |
| 7 | 인증 엔드포인트와 비용이 큰 쿼리에 속도 제한 적용 | 크리덴셜 스터핑과 대량 스크래핑 |
| 8 | 공개 키만 가진 실제 클라이언트에서 재테스트, 마스터 키 사용을 기록하고 검토 | 눈에 띄지 않는 설정 드리프트 |

1–4단계는 [데이터 계층 강제](/glossary/ko/data-layer-vs-application-layer-security/)입니다. 선언적이며 모든 경로에서 검사됩니다. 5–8단계는 운영 규율입니다. 두 절반 모두 필요하며, 어느 한쪽만으로는 충분하지 않습니다.

## 마스터 키 규율과 특권 경로

마스터 키가 존재하는 이유는 마이그레이션, 관리 도구, 예약 작업처럼 정당한 누군가가 관문을 무시할 수 있어야 하기 때문입니다. 규율이란 마스터 키를 그 본질인 루트 자격 증명으로 다루는 것입니다. 설정으로서 서버 환경에 주입하고, 절대 커밋하지 않고, 절대 로그에 남기지 않고, 사용자가 내려받는 어떤 것에도 절대 포함하지 않으며, 세션 전체의 기본값으로 쥐고 있기보다 작업 단위로 사용합니다. 과소평가된 나머지 절반은 회전입니다. 키는 CI 로그와 오래된 노트북으로 조용히 유출되므로, 일정에 따라(그리고 조금이라도 의심되면 즉시) 회전하면 끝내 감지하지 못한 유출의 영향 범위에도 상한이 생깁니다.

Cloud Code는 엄격한 데이터 계층 규칙을 감당할 만하게 만드는 패턴입니다. 클래스를 완전히 잠그고(클라이언트 쓰기는 전혀 없음), 서버리스 함수를 유일한 출입구로 노출합니다. 함수는 입력을 검증하고, 스키마로는 표현할 수 없는 비즈니스 규칙("주문이 발송되기 전까지만")을 강제하고, 상승된 권한으로 쓰고, 감사 기록을 남깁니다. 클래스 관문은 이 단일 통로를 우회할 수 없음을 보장하고, 단일 통로는 로직을 한곳에서 리뷰할 수 있게 유지합니다.

## 일반적인 사용 사례

- **출시 전 잠금.** 위의 체크리스트 전체를 실제 사용자가 오기 전에 한 번 실행합니다. 작은 팀이 쓸 수 있는 보안 시간 중 가장 효과가 큰 한 시간입니다.
- **멀티테넌트 데이터 격리.** 소유자 범위의 ACL과 인증 필수 CLP를 함께 써서 테넌트 A가 테넌트 B를 절대 쿼리할 수 없게 하며, 이를 애플리케이션 코드보다 아래에서 강제합니다.
- **결제와 재고.** 클라이언트 쓰기를 전면 차단한 클래스에, 돈이나 재고에 닿는 모든 것을 위한 감사 가능한 경로로 Cloud Code 함수를 둡니다.
- **장애 대응.** 유출된 키나 로그에서 발견된 이상 징후가 회전, 접근 감사, 외부에서의 권한 테스트 재실행을 촉발합니다.
- **컴플라이언스 증거.** 버전 관리되는 권한 매트릭스와 마스터 키 사용 로그가 "우리는 보안을 진지하게 생각합니다"를 감사인이 읽을 수 있는 산출물로 바꿉니다.

## 데이터 계층과 애플리케이션 코드 중 어디서 강화해야 할까? 결정 매트릭스

| 다음과 같을 때는 데이터 계층(CLP + ACL)에서 강제합니다 | 다음과 같을 때는 애플리케이션 코드(Cloud Code)에서 강제합니다 |
| --- | --- |
| 규칙이 신원과 소유권에 관한 것일 때 | 규칙에 비즈니스 로직이나 여러 객체의 상태가 필요할 때 |
| 향후의 클라이언트를 포함해 모든 경로에서 지켜져야 할 때 | 감사 가능한 단일 통로가 요구 사항일 때 |
| 대시보드 토글 하나가 명세의 전부일 때 | 검증, 데이터 보강, 부수 효과가 함께 따라올 때 |
| 앱을 다시 작성해도 살아남는 보안을 원할 때 | 규칙이 스키마가 바뀌어야 할 속도보다 빠르게 바뀔 때 |
| 잊었을 때의 실패가 재앙적일 때 | 실패가 침해가 아니라 비즈니스 버그일 때 |

실무에서 답은 양자택일이 아니라 계층형입니다. 데이터 계층 규칙은 언제나 버티는 바닥이고, 조건부인 모든 것은 애플리케이션 계층의 함수가 맡습니다. 같은 요청이 두 계층을 모두 통과합니다.

## 한계와 트레이드오프

- **보안 강화는 설정이고, 설정에는 드리프트가 생깁니다.** 새 클래스는 허용적인 기본값으로 생겨납니다. 재감사 습관이 없으면 작년의 잠금이 조용히 침식됩니다.
- **선언적 규칙은 로직을 표현할 수 없습니다.** "소유자만"은 토글이지만, "30일 이내에만 환불 가능"은 코드입니다. 데이터 계층에 지나치게 의존하면 팀은 함수를 작성하는 대신 스키마를 비틀게 됩니다.
- **엄격함에는 개발자 경험 비용이 따릅니다.** 잠긴 클래스와 동결된 스키마는 프로토타이핑을 늦춥니다. 그래서 이 규율은 단계적으로 적용합니다. 개발 중에는 열어 두고, 출시 시점에 잠급니다.
- **속도 제한과 감사에는 조정이 필요합니다.** 너무 빡빡한 제한은 정당한 트래픽 급증을 막고, 읽지 않는 로그는 장식일 뿐입니다. 둘 다 설정 커밋 한 번이 아니라 책임자가 필요합니다.
- **이 계층들은 데이터를 보호할 뿐, 모든 것을 보호하지는 않습니다.** 의존성 취약점, 유출된 사용자 비밀번호, 사회공학은 이 모델 밖에 있습니다. [OWASP API Security Top 10](https://owasp.org/API-Security/) 목록이 분명히 보여 주듯, 데이터 계층 보안 강화는 필요조건이지 충분조건이 아닙니다.

## Back4app에서의 BaaS 보안 강화

Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. 이 글의 체크리스트는 대시보드와 거의 일대일로 대응합니다. 클래스 수준 권한과 보호 필드는 클래스별 체크박스이고, ACL은 SDK, REST, GraphQL 전반에서 모든 요청마다 서버 측에서 강제되며, 마스터 키는 서버 설정에 머물러 특권 경로인 Cloud Code가 작업 단위로 사용할 수 있습니다. [보안 가이드라인](https://www.back4app.com/docs/security/parse-security)이 같은 점검을 단계별로 안내하므로, 백엔드의 보안 강화는 오후 한나절의 토글 작업과 외부 관점에서의 철저한 테스트 한 번으로 끝납니다.
