웹 애플리케이션의 복잡성이 증가함에 따라 적절한 사용자만 Couchbase 데이터에 액세스할 수 있도록 하는 것이 필수적입니다.
잘 정의된 액세스 제어 시스템이 없으면 권한이 없는 사용자가 민감한 정보에 액세스하거나 유해한 작업을 수행할 수 있습니다.
통합 허가 는 Couchbase를 데이터베이스로 활용하는 애플리케이션에서 액세스 제어를 관리하기 위한 강력한 솔루션을 제공합니다. Permit은 권한 부여 프로세스를 간소화하도록 설계되어 개발자가 세분화된 액세스 제어를 구현하는 동시에 Couchbase의 고급 데이터 관리 기능을 활용할 수 있도록 지원합니다.
이 가이드에서는 간단한 요청 시스템을 구축하여 Couchbase 쿼리를 전달하고 규칙 기반 형식 검사를 기반으로 해당 사용자가 해당 쿼리를 실행할 수 있는 액세스 권한이 있는지 확인하는 과정을 안내합니다. 사용자에게 권한이 없는 경우 쿼리 실행이 거부되고, 권한이 있는 경우 계속 진행할 수 있습니다.
카우치베이스란 무엇인가요?
Couchbase는 JSON 문서를 저장하고 관리하는 데 최적화된 강력한 NoSQL 데이터베이스입니다. JSON 객체를 처리할 수 있다는 점이 기존의 키 값 저장소와 차별화되는 점입니다. 이러한 유연성 덕분에 개발자는 비정형 또는 반정형 데이터를 쉽게 저장, 검색, 관리할 수 있어 진화하는 데이터 모델을 가진 애플리케이션에 매우 유용합니다.
Couchbase의 주요 강점 중 하나는 다음과 같습니다. SQL++ 쿼리 기능 는 JSON 문서에 익숙한 SQL과 유사한 구문을 제공합니다(이전에는 N1QL로 알려짐). 이를 통해 개발자는 일반적인 테이블 표현식, 조인 등을 추가할 수 있는 등 JSON 데이터에 대해 강력한 쿼리를 수행할 수 있습니다. 이 Couchbase 쿼리 계층을 사용하면 데이터를 JSON 형식으로 저장하는 동시에 정교한 쿼리를 지원할 수 있습니다.
RBAC란 무엇인가요?
역할 기반 액세스 제어(RBAC) 는 특정 개인이 아닌 역할을 중심으로 시스템 권한을 구성하여 애플리케이션 내부의 권한 관리를 간소화하는 데 도움이 되는 모델입니다. 특정 애플리케이션 또는 조직 내에서 특정 리소스에 대한 액세스를 관리하고 제한할 수 있는 접근 방식을 제공합니다.
Couchbase로 외부 역할 기반 액세스 제어를 구현하려면 다음을 활용합니다. Permit.io는 간단한 UI로 사용자 권한과 역할을 관리할 수 있는 엔드투엔드 솔루션입니다. 사용자가 Couchbase의 도움을 받아 SQL++ 쿼리를 전달하는 Express 패킷으로 React 애플리케이션을 생성하겠습니다. 그런 다음 사용자에게 관련 권한이 있는지 확인합니다. 권한이 없는 경우 메시지를 전송합니다. 권한이 있으면 요청을 수정할 수 있습니다.
카우치베이스 클러스터 설정
첫 번째 단계는 무료 Capella 계정을 만드는 것입니다. 이렇게 하려면 다음 페이지로 이동합니다. cloud.couchbase.com 을 클릭하고 계정 만들기를 클릭하고 이메일과 비밀번호 조합을 사용합니다. GitHub 또는 Google 계정으로 가입할 수도 있습니다.
Capella에서 계정을 생성한 후에는 데이터베이스 클러스터 생성을 진행할 수 있습니다. 데이터베이스 클러스터를 만들려면 무료 티어 옵션을 클러스터에 추가합니다.
계정의 홈 페이지에서 로그인한 후 + 데이터베이스 생성 버튼을 클릭하고 데이터베이스에 선택한 이름을 포함하여 필요한 세부 정보를 입력합니다.
준비가 완료되면 최종 데이터베이스 만들기 버튼을 클릭합니다.
이제 클러스터를 만들었습니다. 다음 단계는 데이터를 저장할 버킷을 추가하는 것입니다. 버킷은 데이터베이스 테이블과 비슷하지만 상당한 차이가 있습니다. 비정형 및 반정형 JSON 데이터로 작업하기 때문에 하나의 버킷에 다양한 유형의 데이터를 담을 수 있습니다.
로 이동하여 데이터 도구 섹션에서 미리 가져온 데이터 집합을 찾을 수 있습니다. 여행 샘플 데이터 세트.
필요한 경우 문서를 더 삽입하거나 완전히 새로운 버킷을 만들 수 있습니다. 하지만 이 데모에서는 여행 샘플 데이터 세트.
다음 그림은 여행 샘플 데이터 세트에 있는 여러 종류의 문서 간의 관계를 보여줍니다. 각 문서에 있는 기본 키, ID 및 유형 필드와 각 문서 유형의 다른 대표 필드를 보여줍니다.
데모에 이 데이터 세트를 사용하겠습니다. 애플리케이션에서 Capella의 데이터와 상호 작용하려면 연결 문자열을 알고 액세스 자격 증명을 만들어야 합니다.
연결 문자열을 찾으려면 연결 버튼을 클릭합니다.
액세스 자격 증명을 추가하려면 아래 그림과 같이 Capella 설정에서 이 페이지로 이동하여 + 데이터베이스 액세스 만들기 버튼을 클릭합니다. 이름과 비밀번호를 입력한 다음 저장. 즉시 자격 증명을 추가해야 합니다. .env 파일을 삭제하면 비밀번호를 다시 검색할 수 없으므로 이 후에는 비밀번호를 삭제하지 마세요.
자격 증명을 생성하면 아카펠라 설정이 완료됩니다. 이제 RBAC 부분으로 이동해 보겠습니다!
Permit.io에서 RBAC 권한 설정하기
다음에서 계정을 설정했는지 확인합니다. Permit.io 를 클릭하고 프로젝트를 만들었습니다. 이 데모에서는 생성된 기본 프로젝트를 사용하겠습니다.
정책 편집기 섹션으로 이동하여 리소스 섹션에서 다음 항목에 해당하는 관련 리소스를 추가합니다. 여행 샘플 데이터 세트.
이 경우 리소스를 다음과 같이 추가합니다. 경로, 공항, 호텔, 그리고 항공사.
관련 작업을 추가했는지 확인합니다.
다음으로 역할 섹션에서 여행 샘플 데이터 세트와 관련된 특정 사용자 역할을 추가할 수 있습니다.
사용 중인 데이터 집합의 경우 다음 역할을 추가해야 합니다: 여행자, 여행사, 항공사 직원 그리고 호텔 직원.
다음으로 디렉토리 섹션으로 이동하여 새 테넌트를 만듭니다. 테넌트를 만든 후에는 역할에 따라 사용자를 추가합니다.
호텔 직원, 여행사, 여행객을 위한 새로운 사용자를 만들 것입니다.
각 사용자에 대해 최상위 액세스 섹션에서 이메일, 이름, 성 및 특정 역할을 추가합니다.
이것이 허가 UI를 사용하여 여행 샘플 데이터 집합에 대한 역할 기반 액세스 제어(RBAC) 모델을 만드는 데 필요한 전부였습니다.
Permit.io로 Couchbase용 세분화된 RBAC 구현하기
이제 Permit을 사용하여 Couchbase SQL++ 쿼리 구문 분석기를 구현해 보겠습니다.
먼저 백엔드 서버를 위해 Express로 Node.js 프로젝트를 설정하겠습니다. Node.js용 Couchbase SDK와 Permit.io SDK를 사용하겠습니다.
다음은 package.json 파일에 필요한 종속성을 추가합니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "name": "backend", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { "cors": "^2.8.5", "couchbase": "^4.4.3", "dotenv": "^16.4.5", "express": "^4.21.1", "permitio": "^2.6.1" } } |
서버 구현하기
주요 구성 요소로 나눠서 살펴보겠습니다:
초기화
먼저 Express 서버를 설정하고 Permit.io 클라이언트를 초기화합니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const express = require('express'); const cors = require('cors'); const { Permit } = require('permitio'); const couchbase = require('couchbase'); require('dotenv').config(); const app = express(); const port = process.env.PORT || 3001; app.use(cors({ origin: 'https://localhost:5173', methods: ['POST'], allowedHeaders: ['Content-Type'], })); // Initialize Permit client const permit = new Permit({ token: process.env.PERMIT_SDK_TOKEN, pdp: "https://cloudpdp.api.permit.io" }); |
SQL++ 쿼리 구문 분석
들어오는 Couchbase 쿼리로 작업하려면 쿼리를 파싱해야 합니다. 입력 쿼리에서 SELECT, FROM, WHERE 절을 추출하는 간단한 파서를 구현했습니다.
구문 분석기의 핵심 구성 요소를 살펴보겠습니다:
쿼리 구문 분석기
QueryParser 클래스는 보안 구현의 기초입니다. 이 클래스는 두 가지 중요한 작업을 처리합니다:
- 쿼리 분석: PSQL++ 쿼리를 사용하여 결정합니다:
-
- 작업 유형 SELECT, UPDATE, DELETE, INSERT
- 액세스 중인 리소스
- 쿼리 구조 유효성 검사
|
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 39 40 |
static parseQuery(query) { // Extract resource from SELECT statement (word before t const selectResourcePattern = /SELECT\s+(\w+)\./i; // Basic regex patterns for SQL operations const patterns = { select: /^\s*SELECT\s+(?:(?!FROM).)*\s+FROM\s+[`]?(\ update: /^\s*UPDATE\s+[`]?(\w+)[`]?(?:\.`?(\w+)`?)?( delete: /^\s*DELETE\s+FROM\s+[`]?(\w+)[`]?(?:\.`?(\w insert: /^\s*INSERT\s+INTO\s+[`]?(\w+)[`]?(?:\.`?(\w }; // Determine operation type and extract resource let operation = ''; let resource = ''; if (patterns.select.test(query)) { operation = 'read'; const resourceMatch = query.match(selectResourcePatt if (resourceMatch && resourceMatch[1]) { resource = resourceMatch[1]; } else { throw new Error('Unable to parse resource from S } } else if (patterns.update.test(query)) { operation = 'update'; const matches = query.match(patterns.update); resource = matches[3] || matches[2] || matches[1]; } else if (patterns.delete.test(query)) { operation = 'delete'; const matches = query.match(patterns.delete); resource = matches[3] || matches[2] || matches[1]; } else if (patterns.insert.test(query)) { operation = 'create'; const matches = query.match(patterns.insert); resource = matches[3] || matches[2] || matches[1]; } if (!operation || !resource) { throw new Error('Unable to parse query operation or } return { query, permission: operation, resource: resource.toLowerCase() }; } |
2. 보안 유효성 검사: SQL 인젝션 및 기타 공격을 방지하기 위해 보안 검사를 구현합니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
static validateQuery(query) { // Basic security validation const disallowedPatterns = [ /;.*;/i, // Multiple statements /--/, // SQL comments /\/\*/, // Block comments /xp_/i, // Extended stored procedur /EXECUTE\s+sp_/i, // Stored procedure executi /EXEC\s+sp_/i, // Stored procedure executi /INTO\s+OUTFILE/i, // File operations /LOAD_FILE/i, // File operations ]; for (const pattern of disallowedPatterns) { if (pattern.test(query)) { throw new Error('Query contains potentially harm } } return true; } |
권한 관리
그리고 트래블 쿼리 검사기 클래스는 핵심 권한 로직을 처리합니다:
- 초기화: Permit.io와 Couchbase 모두에 대한 연결을 설정합니다:
|
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 |
class TravelQueryChecker { constructor() { this.permit = new Permit({ token: process.env.PERMIT_SDK_TOKEN, pdp: "https://cloudpdp.api.permit.io" }); this.clusterConnStr = 'couchbases://cb.6gj2r4ygxyjrfcgf this.username = 'shivay1'; this.password = 'Shivay1234!'; this.bucketName = 'travel-sample'; } async init() { try { this.cluster = await couchbase.connect(this.clusterC username: this.username, password: this.password, configProfile: 'wanDevelopment', }); this.bucket = this.cluster.bucket(this.bucketName); this.collection = this.bucket.defaultCollection(); console.log('Connected to Couchbase Capella'); } catch (error) { console.error('Couchbase connection error:', error); throw error; } } |
- 권한 확인: Permit.io를 통해 사용자 권한을 확인합니다:
|
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 |
async checkQueryPermission(userId, queryConfig) { if (!queryConfig) { return { permitted: false, error: 'Invalid query con } try { const permitted = await this.permit.check( String(userId), queryConfig.permission, { type: queryConfig.resource, tenant: "default", resource: queryConfig.resource } ); return { permitted, query: queryConfig.query, permission: queryConfig.permission, resource: queryConfig.resource }; } catch (error) { console.error('Permission check error:', error); return { permitted: false, error: error.message }; } } |
이 함수는 Permit.io SDK를 사용하여 사용자가 리소스에 대해 특정 작업을 수행할 수 있는 권한이 있는지 확인합니다.
3. 쿼리 실행: 권한 확인부터 쿼리 실행까지 전체 흐름을 처리합니다:
|
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 |
async executeQuery(userId, rawQuery, params = []) { try { //Validate query for security QueryParser.validateQuery(rawQuery); //Parse the query to get permission and resource const queryConfig = QueryParser.parseQuery(rawQuery) console.log('Parsed query config:', queryConfig); const permissionCheck = await this.checkQueryPermiss if (!permissionCheck.permitted) { return { status: 'not-permitted', error: `User ${userId} is not permitted to e }; } const options = { parameters: params }; const result = await this.cluster.query(rawQuery, op return { status: 'permitted', success: true, results: result.rows, metadata: { metrics: result.metrics, profile: result.profile } }; } catch (error) { console.error('Query execution error:', error); return { status: 'error', error: error.message }; } } |
API 엔드포인트
마지막으로 들어오는 쿼리를 처리하기 위해 API 엔드포인트를 노출합니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Route to handle SQL++ queries app.post('/query', async (req, res) => { const { userId, query, params } = req.body; if (!userId || !query) { return res.status(400).json({ error: 'userId and query a } try { const result = await queryChecker.executeQuery(userId, q res.json(result); } catch (error) { res.status(500).json({ error: error.message }); } }); |
카우치베이스 연결
이제 카우치베이스 클러스터에 연결하는 데 도움이 되는 백엔드 Express 서버에서 카우치베이스 연결 코드를 정의하겠습니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Connect to Couchbase cluster async init() { try { this.cluster = await couchbase.connect(this.clusterC username: this.username, password: this.password, configProfile: 'wanDevelopment', }); this.bucket = this.cluster.bucket(this.bucketName); this.collection = this.bucket.defaultCollection(); console.log('Connected to Couchbase Capella'); } catch (error) { console.error('Couchbase connection error:', error); throw error; } } |
프론트엔드 코드
통합 기능을 시연하기 위해 사용자가 쿼리를 입력하고 결과를 확인할 수 있는 간단한 React 프론트엔드를 만들었습니다.
설정은 기존 UI 컴포넌트에서 이 코드를 확인할 수 있습니다:
|
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
import React, { useState } from 'react'; import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" export default function App() { const [query, setQuery] = useState(''); const [user, setUser] = useState(''); const [result, setResult] = useState<null | { success: boolean; results?: any[]; error?: string }>(null); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const response = await fetch('https://localhost:3001/query', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query, user }), }); const data = await response.json(); setResult(data); } catch (error) { console.error('Error:', error); setResult({ success: false, error: 'An error occurred while processing your request.' }); } }; return ( <div className="container mx-auto p-4"> <Card className="w-full max-w-2xl mx-auto"> <CardHeader> <CardTitle>Permission Checker</CardTitle> <CardDescription>Enter a query and user to check permissions</CardDescription> </CardHeader> <CardContent> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label htmlFor="query" className="block text-sm font-medium text-gray-700">Query</label> <Textarea id="query" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Enter your SQL++ query here" className="mt-1" rows={4} /> </div> <div> <label htmlFor="user" className="block text-sm font-medium text-gray-700">User</label> <Input id="user" type="text" value={user} onChange={(e) => setUser(e.target.value)} placeholder="Enter user email" className="mt-1" /> </div> <Button type="submit" className="w-full">Check Permissions</Button> </form> </CardContent> <CardFooter> {result && ( <div className={`mt-4 p-4 rounded ${result.success ? 'bg-green-100' : 'bg-red-100'}`}> {result.success ? ( <div> <h3 className="font-bold text-green-800">Permission Granted</h3> <pre className="mt-2 whitespace-pre-wrap">{JSON.stringify(result.results, null, 2)}</pre> </div> ) : ( <div> <h3 className="font-bold text-red-800">Permission Denied</h3> <p>{result.error}</p> </div> )} </div> )} </CardFooter> </Card> </div> ); } |
이 주어진 React 컴포넌트를 통해 사용자는 쿼리와 사용자 식별자를 입력한 다음 결과 또는 권한 관련 오류를 표시할 수 있습니다.
전체 코드를 보려면 허가-데모 GitHub 리포지토리.
데모
모든 것이 준비되면 백엔드 Express 서버와 React 애플리케이션을 별도로 실행할 수 있습니다. 둘 다 실행되면 아래와 같은 UI가 표시됩니다. 쿼리와 해당 사용자를 추가한 다음 권한 확인 버튼을 클릭합니다.
위의 예에서는 리소스에 액세스할 수 있는 권한이 있는 사용자와 권한이 없는 사용자를 확인할 수 있습니다.
결론
이 튜토리얼에서는 Couchbase 여행 샘플 데이터 세트에 대한 RBAC 설정을 추가하기 위해 권한을 설정 및 구성하는 방법과 주어진 SQL++ 쿼리에 대한 권한을 확인하는 방법을 살펴보았습니다.
사용자별 ID에 초점을 맞춰 사용자 역할보다 더 세분화된 수준의 제어를 원한다면 어떻게 해야 할까요? 그 외에도 다음과 같은 학습 자료를 계속 살펴보는 것이 좋습니다. RBAC와 ABAC의 차이점 그리고 Permit을 사용하여 애플리케이션에 ABAC 추가.
인증 구현에 대해 자세히 알아보고 싶으신가요? 질문이 있으신가요? 문의하기 우리의 Slack 커뮤니티.