카우치베이스 모바일을 사용한 사용자 지정 인증
Couchbase Mobile은 모든 클라우드에서 모든 모바일 장치로 데이터를 안전하게 관리하고 동기화하여 Couchbase를 에지까지 확장합니다. Couchbase Mobile은 임베디드 데이터베이스인 Couchbase Lite를 통해 JSON에 대한 SQL 및 전체 텍스트 검색, 내장된 피어 투 피어 동기화, 클라우드에서 에지까지 엔드투엔드 보안을 제공합니다.
또한 Couchbase Mobile은 웹을 통해 데이터에 액세스하고 동기화할 수 있는 보안 웹 게이트웨이인 동기화 게이트웨이를 제공하며 여러 인증 방법. 이러한 방법 중 하나는 앱 서버가 외부 시스템과의 인증을 처리하는 사용자 지정 인증입니다.
이 블로그 게시물에서는 앱 서버 코드와 모바일 앱 코드를 구현하는 방법의 예시를 통해 사용자 지정 인증 흐름을 안내합니다. An OpenLDAP 서버가 사용되었지만 이 흐름은 모든 외부 인증 제공업체에 적용됩니다.
예제 앱 서버는 간단한 node.js 애플리케이션이며 모바일 앱 코드 샘플은 카우치베이스 라이트 안드로이드 SDK.
전제 조건
이 블로그는 Couchbase Server, 동기화 게이트웨이 및 Couchbase Lite에 익숙하다고 가정합니다.
동기화 게이트웨이 2.5 및 Couchbase Server EE 6.x를 설치해야 합니다. 필요한 경우 다음 링크를 통해 빠르게 설치 및 실행할 수 있습니다:
이 블로그의 모든 코드는 다음 Git 리포지토리에서 사용할 수 있습니다: https://github.com/dugbonsai/sg-custom-auth
사용자 지정 인증 흐름
다음 다이어그램은 사용자 지정 인증의 예를 보여줍니다.

- 모바일 앱 사용자는 앱 서버에 의해 노출된 REST 인터페이스를 통해 자격 증명을 사용하여 로그인을 시도합니다.
- 앱 서버는 OpenLDAP 서버에 대해 사용자를 인증합니다.
- 사용자가 인증되지 않은 경우 로그인 UI로 돌아갑니다.
- 사용자가 인증되면 앱 서버는 REST 인터페이스를 통해 동기화 게이트웨이에서 사용자 정보를 가져옵니다.
- 동기화 게이트웨이에 사용자가 있는 경우 8단계로 건너뜁니다.
- 사용자가 동기화 게이트웨이에 존재하지 않는 경우 앱 서버는 REST 인터페이스를 통해 동기화 게이트웨이에 새 사용자를 만듭니다.
- 사용자가 동기화 게이트웨이에서 생성되지 않은 경우 앱 서버는 오류를 반환하고 사용자는 로그인 UI로 돌아갑니다.
- 사용자가 동기화 게이트웨이에서 생성된 경우, 앱 서버는 REST 인터페이스를 통해 동기화 게이트웨이에서 사용자에 대한 새 세션을 만듭니다.
- 동기화 게이트웨이에서 세션이 생성되지 않은 경우 앱 서버는 오류를 반환하고 사용자는 로그인 UI로 돌아갑니다.
- 세션이 동기화 게이트웨이에서 생성된 경우 앱 서버는 세션 ID를 반환합니다.
- 모바일 앱은 세션 ID를 저장하고 세션 ID를 사용하여 Couchbase Lite 리플리케이터를 생성합니다.
- 동기화 게이트웨이에서 세션이 만료되어 복제에 실패하면 사용자는 로그인 UI로 돌아갑니다.
OpenLDAP 구성
이미 LDAP 서버를 구성한 경우 이 섹션을 건너뛸 수 있습니다. LDAP 서버를 구성하지 않은 경우, 이 지침은 Ubuntu 18.04에서 OpenLDAP를 구성하는 방법을 안내합니다..
- LDIF 파일 생성(ldap_data.ldif)에 LDAP 데이터베이스를 채울 정보(LDIF 파일에 대한 자세한 내용을 보려면 여기를 클릭하세요.). 위의 OpenLDAP 구성 지침에는 LDAP 데이터베이스를 채우기 위한 LDIF 파일 생성 지침도 포함되어 있습니다. 이 예에서는 모바일 앱에서 인증할 때 모바일 사용자(13번째 줄부터 시작)가 사용됩니다.
|
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 |
dn: ou=People,dc=example,dc=com objectClass: organizationalUnit ou: People dn: ou=Groups,dc=example,dc=com objectClass: organizationalUnit ou: Groups dn: cn=department,ou=Groups,dc=example,dc=com objectClass: posixGroup cn: subgroup gidNumber: 5000 dn: uid=mobileuser,ou=People,dc=example,dc=com objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: mobileuser sn: User givenName: Mobile cn: Mobile User displayName: mobileuser uidNumber: 10000 gidNumber: 5000 userPassword: password gecos: Mobile User loginShell: /bin/bash homeDirectory: /home/mobileuser |
- 초기 데이터를 OpenLDAP 데이터베이스에 추가합니다:
|
1 |
$ ldapadd -x -D cn=admin,dc=example,dc=com -W -f ldap_data.ldif |
- OpenLDAP 디렉토리에서 mobileuser를 검색하여 설정을 테스트합니다:
|
1 |
$ ldapsearch -x -LLL -b dc=example,dc=com 'uid=mobileuser' |
모바일 사용자에 대한 정보가 표시됩니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
dn: uid=mobileuser,ou=People,dc=example,dc=com objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount uid: mobileuser sn: User givenName: Mobile cn: Mobile User displayName: mobileuser uidNumber: 10000 gidNumber: 5000 gecos: Mobile User loginShell: /bin/bash homeDirectory: /home/mobileuser |
앱 서버 구현
앱 서버는 node.js 애플리케이션으로 구현됩니다. 전체 앱 서버 코드는 다음에서 확인할 수 있습니다. auth.js 를 다음 Git 리포지토리에 저장합니다: sg-custom-auth. 앱 서버는 여권-ldapauth 패키지 를 사용해야 합니다. 다음 코드 스니펫은 OpenLDAP 서버에 대한 인증 호출과 동기화 게이트웨이 REST API 호출을 강조 표시합니다.
앱 서버는 POST /로그인 호출을 처리해야 합니다. 이 엔드포인트는 요청 본문에 사용자 이름과 비밀번호로 저장된 사용자 자격 증명을 사용하여 모바일 앱에서 호출됩니다. 앱 서버는 이러한 자격 증명을 사용하여 OpenLDAP 서버에 대해 인증합니다.
searchBase의 값은 LDAP 서버에 정의된 도메인 구성 요소와 일치해야 합니다. 위에서 dc=example,dc=com을 사용했으므로 여기(12줄)에서도 동일한 값을 사용합니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var express = require('express'), passport = require('passport'), bodyParser = require('body-parser'), LdapStrategy = require('passport-ldapauth'), curl = require('curl-request'); var syncGatewayEndpoint = 'https://<Sync Gateway Host>:4985/<db>'; var app = express(); var OPTS = { server: { url: 'ldap://<OpenLDAP Host>:389', searchBase: 'dc=example,dc=com', // MUST match LDAP directory searchFilter: '(uid={{username}})' } }; passport.use(new LdapStrategy(OPTS)); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false})); app.use(passport.initialize()); app.post('/login', passport.authenticate('ldapauth', {session: false}), function(req, res) { // user successfully authenticated against LDAP } |
사용자가 인증되면 다음 단계가 실행됩니다.
동기화 게이트웨이에서 사용자 정보 가져오기
다음을 호출하여 동기화 게이트웨이에서 사용자 정보를 가져옵니다. GET /{db}/_user/{name}. 사용자가 동기화 게이트웨이에 있는 경우 응답 코드는 200입니다. 사용자가 동기화 게이트웨이에 존재하지 않는 경우 응답 코드는 404입니다.
|
1 2 3 4 5 6 7 8 9 10 |
getUser = new(curl); getUser.setHeaders(['Content-Type: application/json']) .get(syncGatewayEndpoint + '/_user/' + req.body.username) .then(({statusCode, body, headers}) => { if (statusCode == 404) { // status == 404: user does not exist } else if (statusCode == 200) { // status == 200: user exists } }) |
동기화 게이트웨이에 사용자가 없는 경우 새 사용자를 만듭니다.
동기화 게이트웨이에서 다음을 호출하여 새 사용자를 만듭니다. POST /{db}/_user/. 동기화 게이트웨이에서 사용자가 성공적으로 생성된 경우 응답 코드는 201입니다.
|
1 2 3 4 5 6 7 8 9 10 11 |
postUser = new(curl); postUser.setHeaders(['Content-Type: application/json']) .setBody('{"name": "' + req.body.username + '","password": "' + req.body.password + '"}') .post(syncGatewayEndpoint + '/_user/') .then(({statusCode, body, headers}) => { if (statusCode == 201) { // status == 201: success } else { // user could not be created } }) |
동기화 게이트웨이에서 사용자에 대한 세션 만들기
동기화 게이트웨이에서 사용자에 대한 세션을 만듭니다. POST /{db}/_session. 이 예에서는 세션이 30분 후에 만료됩니다. 동기화 게이트웨이에서 세션이 성공적으로 생성된 경우 응답 코드는 200입니다. JSON 응답에는 다음 값이 포함됩니다:
세션_id새 세션의 ID입니다. 모바일 앱에서 동기화 게이트웨이를 통한 인증에 사용됩니다.
만료: 세션이 만료될 때의 타임스탬프입니다. 모바일 앱에서는 사용되지 않습니다.
쿠키_이름: 세션 쿠키의 이름입니다. 모바일 앱에서는 사용되지 않습니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
resBody = {statusCode: 200}; postSession = new(curl); postSession.setHeaders(['Content-Type: application/json']) .setBody('{"name": "' + request.body.username + '","ttl": 1800}') .post(syncGatewayEndpoint + '/_session') .then(({statusCode, body, headers}) => { if (statusCode == 200) { // status == 200: success resBody.session_id = body.session_id; resBody.expires = body.expires; resBody.cookie_name = body.cookie_name; // send success response back to client response.send(JSON.stringify(resBody)); } else { // session could not be created } }) |
인증 테스트
앱 서버에서 인증을 테스트하려면 다음 명령을 사용하여 시작하세요:
|
1 |
$ node auth.js |
시작되면 다음과 같은 내용이 표시됩니다:
|
1 |
Listening on port 8080 |
다음과 같이 간단한 curl 명령어(또는 Postman 등)를 사용하여 앱 서버를 테스트할 수 있습니다:
|
1 |
$ curl -d "username=mobileuser&password=password" -X POST https://<AppServer host>:8080/login |
다음과 유사한 출력이 표시됩니다:
|
1 |
{"statusCode":200,"session_id":"c149012eaa8d0cf15b1b4110cf0a2fec259ef726","expires":"2019-08-01T13:47:56.311076773Z","cookie_name":"SyncGatewaySession"} |
사용자가 LDAP에 존재하지 않으면 다음과 같은 응답이 표시됩니다:
|
1 |
Unauthorized |
앱 서버의 출력은 다음과 같습니다:
|
1 2 3 4 5 |
mobileuser has been authenticated with LDAP creating user mobileuser in Sync Gateway user mobileuser created in Sync Gateway create session for user mobileuser created session for mobileuser |
모바일 앱 구현
남은 작업은 모바일 앱에서 앱 서버 및 동기화 게이트웨이로 적절한 호출을 하는 것뿐입니다. 모바일 앱은 발리 프레임워크 를 사용하여 앱 서버에 REST 호출을 수행합니다. 다음 코드 스니펫은 앱 서버에 대한 호출과 세션_id를 사용하여 인증하는 Couchbase Lite 코드를 강조 표시합니다.
모바일 사용자 인증
POST /login/을 호출하여 앱 서버로 사용자를 인증합니다.
|
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 |
JSONObject reqBody = new JSONObject(); reqBody.put("username", <user supplied username>); reqBody.put("password", <user supplied password>); String url = <App Server host>:8080/login; RequestQueue queue = Volley.newRequestQueue(<context>); JsonRequest<JSONObject> jsonRequest = new JsonObjectRequest( Request.Method.POST, url, reqBody, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { // get session_id from response try { String sessionID = response.getString("session_id"); // Store session_id } catch (JSONException je) { // Handle exception } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // authentication failed } }); queue.add(jsonRequest); |
원샷 복제 생성
저장된 세션_id(13줄)를 사용하여 원샷 복제를 생성하고 동기화 게이트웨이 세션이 만료된 경우 다시 인증합니다.
|
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 |
String syncGatewayEndpoint = "ws://<Sync Gateway Host>:4984/{db}"; URI url = null; try { url = new URI(mSyncGatewayEndpoint); } catch (URISyntaxException e) { e.printStackTrace(); return; } ReplicatorConfiguration config = new ReplicatorConfiguration(database, new URLEndpoint(url)); config.setReplicatorType(ReplicatorConfiguration.ReplicatorType.PUSH_AND_PULL); config.setContinuous(false); config.setAuthenticator(new SessionAuthenticator(sessionID)); Replicator replicator = new Replicator(config); replicator.addChangeListener(new ReplicatorChangeListener() { @Override public void changed(ReplicatorChange change) { CouchbaseLiteException error = change.getStatus().getError(); if (error != null) { if (error.getCode() == 10401) { // session expired; re-authenticate } } ... } }); replicator.start(); |
다음 단계
Couchbase를 처음 사용하는 경우 다음에서 제공되는 무료 온라인 교육을 활용하세요. https://learn.couchbase.com 를 클릭해 자세히 알아보세요.
자세히 보기 모바일 튜토리얼 에서 찾을 수 있습니다. 카우치베이스 튜토리얼 웹 페이지.