102 세션의 카우치베이스 라이브 뉴욕 모바일 트랙의 코드에 설명된 Couchbase Mobile 샘플 애플리케이션을 계속 반복했습니다. "카우치베이스 모바일 101: 첫 번째 모바일 앱을 구축하는 방법" 세션.
카우치베이스 모바일 102 세션에서는 다음과 같은 내용을 살펴보았습니다. 동기화 게이트웨이 의 기능에 대해 자세히 알아보고, 깃허브 리포지토리에서 찾을 수 있는 Grocery Sync 샘플 애플리케이션을 통해 "모바일 애플리케이션에 보안 동기화를 추가하는 방법"에 대해 자세히 알아보세요. iOS 그리고 Android. 이 블로그에서는 동기화 게이트웨이에 대해 설명한 내용을 살펴봅니다. 동기화 게이트웨이는 Couchbase Lite와 Couchbase Server를 하나로 묶는 구성 요소입니다. 슬라이드를 참조하세요. 에 대한 주제와 코드 스니펫에 대해 설명합니다.
주요 모바일 데이터 보안 문제
동기화 게이트웨이가 해결하는 데 도움이 되는 영역 중 하나는 데이터 복제이며, 이는 클라우드에서 장치의 모바일 애플리케이션으로 데이터가 앞뒤로 동기화되는 방식에 관한 것입니다. 복제가 이루어지기 전 사용자 인증은 데이터 파티셔닝이 이루어질 때 동기화 게이트웨이가 처리하는 또 다른 핵심 영역입니다. 복제가 완료되면 사용자가 특정 데이터를 배포할 위치를 결정할 수 있도록 데이터를 적절히 파티셔닝해야 합니다. 그리고 데이터 액세스 제어가 있는데, 인증된 사용자에게 동기화 게이트웨이가 그에 따라 읽기 및 쓰기 권한을 설정하는 데 도움을 줄 수 있습니다. 각각에 대해 자세히 살펴보겠습니다.
[1] 사용자 인증
카우치베이스 모바일이 지원하는 플러그형 인증 모델을 사용하면 동기화 게이트웨이에서 애플리케이션 아키텍처의 보안 프레임워크에 대한 사용자 지정 구성이 가능한 다양한 방법으로 인증을 구현할 수 있습니다. 동기화 게이트웨이는 '기본 인증', 'Facebook', '페르소나' 인증 모델을 통해 세 가지 퍼블릭 공급업체를 지원합니다..
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "facebook" : { "등록" : false }, "데이터베이스": { "grocery-sync": { "서버":"http://cbserver:8091", "버킷":"식료품-동기화", "사용자": {"게스트": {"disabled": true}}, "동기화":`함수(doc) {채널(doc.채널);}` } } } |
많은 보안 질문은 읽기 및 쓰기 액세스를 관리하는 방법에 관한 것입니다. 동기화 게이트웨이를 사용하면 문서 수준에서 읽기 측 정책에 대한 세분화된 보안 정책을 정의한 다음 필드 수준까지 쓰기 측 정책을 정의할 수 있습니다. 일반적인 정책 시행 프레임워크는 복잡한 보안 규칙을 유연하게 정의하여 Couchbase Lite를 실행하는 모바일 애플리케이션으로 확장할 수 있는 Javascript 동기화 기능을 기반으로 합니다.
동기화 기능: 동기화 게이트웨이 구성
동기화 함수는 읽기-쓰기 액세스를 관리하는 방법에 대한 동기화 게이트웨이의 핵심이며 대부분의 데이터 액세스 규칙이 정의되는 곳입니다. 따라서 동기화 함수는 JSON 문서가 동기화 게이트웨이에 기록될 때마다 실행되는 JavaScript 함수이며, 보안 구현의 핵심입니다.
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "데이터베이스": { "grocery-sync": { "서버":"http://walrus:", "버킷":"식료품-동기화", "사용자": {"게스트": {"disabled": true}}, "동기화":`함수(doc,oldDoc) { 채널(doc.채널); }` } } } |
이 함수는 동기화 게이트웨이 구성 파일에 정의되어 있으며, 메서드 서명은 문서의 현재 수정본인 doc와 이전 수정본인 oldDoc을 취합니다. 메서드 본문은 이 두 가지 입력을 기반으로 보안 규칙이 정의되는 곳입니다. 여기에서 간단한 기본 동기화 기능을 먼저 정의한 다음 모든 경우에 적용할 수 있는 고급 보안 규칙을 서서히 구축할 수 있습니다. 이 접근 방식을 사용하면 새로운 문서 유형이 정의되거나 JSON 스키마가 변경되는 경우 동기화 함수를 수정할 수 있습니다.
동기화 기능: 쓰기 권한
- 요구 사용자(): 사용자 ID 목록을 입력으로 받고 현재 활성 사용자가 해당 목록에 속하는지 확인합니다.
- requireRole(): 요구 사용자에게 특정 역할이 부여되었는지 확인할 수 있는 역할 목록을 입력으로 받습니다. 부여되지 않은 경우 문서가 거부됩니다.
- requireAccess(): 현재 사용자의 목록과 채널 목록을 입력으로 받아 특정 채널이 부여되었는지 확인합니다. 사용자가 목록에 포함되지 않으면 거부됩니다.
- throw(): 던지기를 사용하면 들어오는 문서에 대해 원하는 검사를 수행할 수 있습니다. 예를 들어 문서의 유형이 'item'인 경우 유효성 검사를 수행하여 유형이 일치하지 않으면 문서를 거부할 수 있습니다. 마찬가지로 특정 범위 내에 있는 값을 기반으로 유효성 검사를 수행할 수도 있습니다. 따라서 동기화 함수에서 구현할 수 있는 매우 세분화된 필드 수준 유형 유효성 검사는 해당 기준을 충족하지 않는 문서에 throw() 메서드와 함께 사용할 수 있습니다. throw()를 사용하면 오류가 발생한 이유와 문서가 거부된 이유에 대한 오류 세부 정보도 제공할 수 있습니다.
동기화 기능: 읽기 권한

채널 명령을 사용하여 사용자를 채널에 할당하는 즉시 특정 채널이 존재하게 됩니다. 이 시점에서 사용자는 동일한 채널 이름으로 태그가 지정된 문서를 찾게 됩니다. 별표[*]와 느낌표[!] 매개변수가 있는 두 개의 특수 채널이 존재합니다.
- 스타[*]: 스타 채널은 모든 문서가 해당 채널에 자동으로 추가되는 채널입니다. 예를 들어 게스트 사용자에게 * 별표 채널이 부여되면 모든 문서에 대한 개방형 액세스 권한을 갖게 됩니다. 이 채널 속성의 명확한 사용 사례는 채널에 * 별표가 있는 모든 사용자가 시스템의 모든 문서를 볼 수 있는 관리자 권한과 유사합니다.
- 느낌표[!] 두 번째 특수 채널은 ! 느낌표 속성으로, 문서를 공개 채널에 추가하면 모든 사용자가 해당 특정 문서를 볼 수 있는 문서 관점에서 초점을 맞춘 공개 채널을 설명하는 데 사용됩니다. 이 채널 속성의 명확한 사용 사례는 공개 공지를 위한 문서를 만들거나 모든 사용자에게 메시지를 방송하는 것과 유사합니다.
동기화 기능: 예제
다음 예제에서는 다음과 같이 살펴보겠습니다. 위에서 설명한 기능을 사용하여 보안을 유지하는 방법 식료품 동기화 애플리케이션은 Couchbase Mobile 101 세션에서 다음과 같이 존재합니다. 개인 정보가 보호되지 않는 완전히 보안되지 않은 애플리케이션입니다. 동기화 게이트웨이를 반복하면 사용자는 추가되는 모든 항목이 동일한 목록에 표시되는 것이 아니라 자신의 항목만 보게 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "log" : ["*"], "데이터베이스": { "grocery-sync": { "서버":"바다코끼리:", "버킷":"식료품-동기화", "사용자": { "게스트": { "disabled": false, "관리자_채널" : ["*"] } } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["*"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["*"] } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["*"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["*"] } }, "동기화" : ' 함수(doc, oldDoc) { // 플레이스홀더 동기화 기능 추가, 여기에 사용자 지정 읽기/쓰기 로직 추가 }' } } } |
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 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { // 플레이스홀더 동기화 기능 추가, 여기에 사용자 지정 읽기/쓰기 로직 추가 } ' } } } |
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 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { 채널("항목-"+doc.소유자); } //소유자의 아이템 채널에 아이템 문서 추가하기 ' } } } |
문서가 동기화 게이트웨이에 작성되면 채널 이름이 '항목' 접두사가 붙은 형식의 채널에 문서를 할당합니다. 를 "doc.owner"에서 가져온 소유자 문서 내의 소유자 속성 값으로 대체할 수 있습니다. 이전 구성은 문서 속성이 'bob'으로 설정되어 있고 'items'가 'items-bob' 채널에 할당된 경우입니다. 사용자 'bob'은 'items-bob' 채널에 액세스할 수 있으며 기본적으로 이 채널은 Bob이 자신의 사용자 기록에서 액세스 권한을 부여받은 채널입니다. 이제 소유자가 'bob'인 모든 항목은 Bob이 해당 채널을 통해 볼 수 있지만 'items-alice' 채널에 있지 않으므로 Alice는 볼 수 없습니다. 이 구성에서 발생한 것은 문서를 적절한 채널에 할당하고 소유자 'items-' 채널을 통해 소유자에게 프로그래밍 방식으로 액세스 권한을 부여했기 때문입니다.
이제 사용자는 자신의 항목과 사실상 자신의 식료품 목록만 보거나 읽을 수 있습니다. 하지만 여전히 서로의 목록에 글을 쓸 수 있는데, Bob이 항목을 업로드하고 소유자 속성을 'Alice'로 설정하면 사실상 Bob이 Alice의 장보기 목록에 쉽게 침입할 수 있기 때문입니다. 보안을 강화하기 위한 다음 단계는 문서가 들어올 때 소유자 속성이 현재 인증된 사용자와 일치하는지 확인하는 것입니다.
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 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { 요구 사용자(doc.소유자); //아이템 문서의 소유자는 인증된 사용자여야 합니다. 채널("항목-"+doc.소유자); } ' } } } |
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 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { 만약 (doc.유형 == "친구") { //새 친구 문서 처리 요구 사용자(doc.소유자); //친구 소유자 액세스(doc.친구, "항목-"+doc.소유자); 채널("비공개-"+doc.소유자); 액세스(doc.소유자, "비공개-"+doc.소유자) } else { 요구 사용자(doc.소유자) 채널("항목-"+doc.소유자); } } ' } } |
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 32 33 34 35 36 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { 만약 (doc.유형 == "친구") { //새 친구 문서 처리 요구 사용자(doc.소유자); //친구 소유자 액세스(doc.친구, "항목-"+doc.소유자); 채널("비공개-"+doc.소유자); 액세스(doc.소유자, "비공개-"+doc.소유자); } else 만약 (doc.유형 == "항목") { 요구 사용자(doc.소유자); 채널("항목-"+doc.소유자); } else{ throw({금지됨: "유효하지 않음 문서 유형"}); } } }' } } |
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 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { 만약 (doc.유형 == "친구") { //새 친구 문서 처리 요구 사용자(doc.소유자); //친구 소유자 액세스(doc.친구, "항목-"+doc.소유자); 채널("비공개-"+doc.소유자); 액세스(doc.소유자, "비공개-"+doc.소유자); } else 만약 (doc.유형 == "항목") { requireAccess("항목-"+doc.소유자) 채널("항목-"+doc.소유자); } else{ throw({금지됨: "유효하지 않음 문서 유형"}); } } ' } } |
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 32 33 34 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { 만약 (doc.유형 == "친구") { //새 친구 문서 처리 요구 사용자(doc.소유자); //친구 소유자 액세스(doc.친구, "항목-"+doc.소유자); 채널("비공개-"+doc.소유자); 액세스(doc.소유자, "비공개-"+doc.소유자); } else 만약 (doc.유형 == "항목") { requireAccess("항목-"+doc.소유자) 만약 (oldDoc == null) { 만약 (doc.확인 == true) { throw( {금지됨: "new 항목 할 수 없음 be 확인됨"}); } } 채널("항목-"+doc.소유자); } else { throw( {금지됨: "유효하지 않음 문서 유형"}); } } ' } } |
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 32 33 34 35 36 37 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { 만약 (doc.유형 == "친구") { //새 친구 문서 처리 요구 사용자(doc.소유자); //친구 소유자 액세스(doc.친구, "항목-"+doc.소유자); 채널("비공개-"+doc.소유자); 액세스(doc.소유자, "비공개-"+doc.소유자); } else 만약 (doc.유형 == "항목") { requireAccess("항목-"+doc.소유자) 만약 (oldDoc == null) { 만약 (doc.확인 == true) { throw( {금지됨: "new 항목 할 수 없음 be 확인됨"}); } else { 만약 (doc.확인 != oldDoc.확인) { 요구 사용자(doc.소유자); } } } 채널("항목-"+doc.소유자); } else { throw( {금지됨: "유효하지 않음 문서 유형"}); } } ' } } |
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 32 33 34 35 36 37 38 |
{ "로그" : ["*"], "데이터베이스": { "식료품-동기화": { "서버":"월러스:", "bucket":"grocery-sync", "users": { "alice": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-alice"] }, "bob": { "disabled" : false, "비밀번호": "비밀번호", "관리자_채널":["항목-bob"] } }, "동기화" : ' 함수(doc, oldDoc) { 만약 (doc.유형 == "친구") { //새 친구 문서 처리 요구 사용자(doc.소유자); //친구 소유자 액세스(doc.친구, "항목-"+doc.소유자); 채널("비공개-"+doc.소유자); 액세스(doc.소유자, "비공개-"+doc.소유자); } else 만약 (doc.유형 == "항목") { requireAccess("항목-"+doc.소유자) 만약 (oldDoc == null) { 만약 (doc.확인 == true) { throw( {금지됨: "new 항목 할 수 없음 be 확인됨"}); } else { 만약 (doc.소유자 != oldDoc.소유자) { throw({금지됨: "종료 훔치기 항목"}); } 만약 (doc.확인 != oldDoc.확인) { 요구 사용자(doc.소유자); } } } 채널("항목-"+doc.소유자); } else { throw( {금지됨: "유효하지 않음 문서 유형"}); } } ' } } |
[3] 유선을 통한 데이터 전송
이제 보안과 관련된 사용자 액세스에 대한 인증 및 데이터 읽기/쓰기 문제를 제외하고, 다음 주제는 원격 엔드포인트로 전송되는 데이터의 보안을 보장하는 것입니다. 동기화 게이트웨이는 전송 시 SSL 및 TLS를 지원합니다. 모바일 앱의 데이터 보안을 강화하기 위해 Sync Gateway에서 SSL을 사용하도록 Sync Gateway 구성 파일 내에서 구성하는 것은 간단합니다.
[4] 데이터 스토리지
클라이언트의 데이터 저장소, 를 사용하면 실제로 장치에서 파일 시스템 암호화를 수행하고 있는 것입니다. 모바일 개발자 포털에 로컬 카우치베이스 라이트 데이터베이스를 암호화하기 위해 수행해야 하는 작업에 대한 좋은 정보가 있습니다. 여기서는 안전한 클라우드 환경과 클라이언트에서 파일 시스템 암호화를 위한 구성에 대해 설명합니다.
요약
동기화 게이트웨이의 진수 임베디드 장치에 있는 프레임워크인 Couchbase Lite를 하나로 묶어줍니다. 클라우드에서 실행 중인 Couchbase 서버가 있고 Sync Gateway가 이를 서로 연결합니다. 동기화 게이트웨이에는 로컬 장치에서 실행 중인 애플리케이션(독립형 연결 해제 애플리케이션)을 완전한 기능을 갖춘 다중 사용자 동기화 환경으로 전환하기 위해 제공하는 몇 가지 주요 기능이 있습니다. 위에 표시된 것은 동기화 함수 구성 파일을 반복하여 데이터베이스의 항목을 직렬화하는 방법입니다.
다음으로 살펴볼 Couchbase Lite HTTP 리스너 컴포넌트 클래스는 "Couchbase Mobile로 피어투피어 앱 구축"을 통해 고유한 소셜 인앱 경험을 만들 수 있는 피어투피어 기능을 활성화하는 방법에 대한 Couchbase Mobile 103 세션입니다.