분류

Node.js를 사용한 게임 서버 및 카우치베이스 - 2부

이번 시리즈에서는 세션 관리와 인증된 엔드포인트(로그인해야 하는 엔드포인트)를 구현해 보겠습니다. 시작해 보겠습니다!

아직 읽지 않았다면 1부 의 기본 프로젝트 레이아웃과 기본 사용자 관리를 설정하고 2부의 전제 조건이 되므로 꼭 읽어보시길 권해드립니다!

세션 관리 - 모델

세션 관리 엔드포인트를 구축하기 위한 첫 번째 단계는 이러한 엔드포인트에서 데이터베이스를 조작하는 데 사용할 모델을 설정하는 것입니다. 계정 모델과 달리 이 모델은 참조 문서나 복잡한 로직이 필요하지 않으므로 매우 간단합니다. 첫 번째 단계는 필요한 다양한 모듈을 가져오고 데이터베이스 모듈에서 데이터베이스 연결에 대한 참조를 가져오는 것입니다.

var db = require('./../데이터베이스').메인버킷;
var 카우치베이스 = require('couchbase');
var uuid = require('uuid');

다음으로 계정 모델과 마찬가지로 데이터베이스 속성을 제거하기 위한 작은 함수를 작성합니다. 이 경우에도 제거해야 하는 속성은 'type' 하나뿐입니다.

함수 cleanSessionObj(객체) {
삭제 obj.유형;
반환 객체;
}

이제 세션 모델 클래스 자체에 대한 작업을 시작하겠습니다. 먼저 빈 생성자를 만들겠습니다. 이 시점에서 이 예제에서는 모델 클래스가 현재 완전히 정적이라는 점을 언급하고 싶지만, 정적 CRUD 작업에서 모델 클래스를 반환하는 것이 좋은 방법이지만 아직은 이것이 도움이 될 만한 시점이 아닙니다.

함수 세션 모델() {
}
모듈.수출 = 세션 모델;

이제 실제로 어떤 작업을 수행할 첫 번째 세션 모델 함수를 살펴보겠습니다.

세션 모델.create = 함수(uid, 콜백) {
};

그리고 그 함수 내에서 삽입할 문서와 관련 키를 만들어야 합니다(여기서는 아이디만 저장한 다음 계정 모델을 통해 계정 문서에서 직접 사용자의 나머지 정보에 액세스할 수 있습니다(나중에 보게 될 것입니다).

var sessDoc = {
유형: '세션',
sid: uuid.v4(),
uid: uid
};
var sessDocName = 'sess-' + sessDoc.sid;

그런 다음 새로 만든 세션 문서를 클러스터에 저장합니다. 또한 위에서 살균 기능을 호출하고 만료 값을 60분으로 설정한 것을 알 수 있습니다. 이렇게 하면 클러스터가 해당 시간이 지나면 세션을 제거하여 세션을 '만료'합니다.

db.추가(sessDocName, sessDoc, {만료: 3600}, 함수(err, 결과) {
콜백(err, cleanSessionObj(sessDoc), 결과.cas);
});

다음으로 이전에 저장한 세션 정보를 검색할 수 있는 함수를 모델에 구축해야 합니다. 이를 위해 이전에 저장했던 키와 일치하는 키를 생성한 다음, 세션의 사용자 ID를 콜백으로 반환하는 get 요청을 실행합니다.

세션 모델.get = 함수(sid, 콜백) {
var sessDocName = 'sess-' + sid;db.get(sessDocName, 함수(err, 결과) {
만약 (err) {
반환 콜백(err);
}

콜백(null, 결과..uid);
});
};

완성된 세션모델.js는 다음과 같습니다:

var db = require('./../데이터베이스').메인버킷;
var 카우치베이스 = require('couchbase');
var uuid = require('uuid');함수 cleanSessionObj(객체) {
삭제 obj.유형;
반환 객체;
}

함수 세션 모델() {
}

세션 모델.create = 함수(uid, 콜백) {
var sessDoc = {
유형: '세션',
sid: uuid.v4(),
uid: uid
};
var sessDocName = 'sess-' + sessDoc.sid;

db.추가(sessDocName, sessDoc, {만료: 3600}, 함수(err, 결과) {
콜백(err, cleanSessionObj(sessDoc), 결과.cas);
});
};

세션 모델.get = 함수(sid, 콜백) {
var sessDocName = 'sess-' + sid;

db.get(sessDocName, 함수(err, 결과) {
만약 (err) {
반환 콜백(err);
}

콜백(null, 결과..uid);
});
};

모듈.수출 = 세션 모델;

세션 관리 - 계정 조회

요청 핸들러 자체를 작성하기 전에 사용자 이름을 기반으로 사용자를 조회할 수 있는 메서드를 작성해야 합니다. 사용자가 자신의 사용자 아이디를 기억할 필요가 없기를 바랍니다! 1부에서는 accountmodel.js에서 계정 모델을 구축했습니다. 그 파일로 돌아가서 새로운 메서드를 추가해 보겠습니다.

계정 모델.사용자 이름 = 함수(사용자 이름, 콜백) {
};

이 방법을 처리하는 방법은 제공된 사용자 이름을 사용하여 생성한 참조 문서 중 하나를 검색하는 키를 생성하는 것입니다. AccountModel.create. 이 참조 문서를 찾을 수 없는 경우 사용자가 존재하지 않는 것으로 간주하고 오류를 반환합니다. 사용자 이름을 찾을 수 있고 참조 문서를 찾을 수 있으면 AccountModel.get 를 사용하여 사용자 문서 자체를 찾고 콜백을 전달합니다. 즉, 다음과 같은 호출은 AccountModel.getByUsername 는 마치 직접 호출한 것처럼 전체 사용자 객체를 반환합니다. AccountModel.get UID를 사용합니다.

전체 기능은 다음과 같습니다:

계정 모델.사용자 이름 = 함수(사용자 이름, 콜백) {
var refdocName = '사용자 이름-' + 사용자 이름;
db.get(refdocName, 함수(err, 결과) {
만약 (err && err.코드 === 카우치베이스.오류.keyNotFound) {
반환 콜백('사용자 이름을 찾을 수 없음');
} else 만약 (err) {
반환 콜백(err);
}// 찾은 UID를 추출합니다.
var foundUid = 결과..uid;

// 일반 가져오기로 전달
계정 모델.get(foundUid, 콜백);
});
};

세션 관리 - 요청 처리

세션 생성을 활성화하는 마지막 단계는 요청 핸들러 자체를 작성하는 것입니다. 다행히도 대부분의 중요한 로직은 위의 섹션에 포함되어 있으며, 요청 핸들러는 처리를 위해 정보를 각각에 전달하는 간단한 작업을 수행합니다. 먼저 사용자의 입력을 검증하여 필요한 모든 정보가 제공되었는지 확인합니다. 다음으로 제공된 사용자 아이디로 계정을 찾습니다. 다음으로 제공된 비밀번호를 해시하고 비교하여 비밀번호가 사용자가 제공한 것과 일치하는지 확인합니다. 마지막으로 새로 만든 세션 모델을 사용하여 세션을 생성하고 사용자에게 세부 정보를 반환합니다. 세션 ID가 사용자에게 직접 제공되지 않고 헤더에 전달되는 것을 볼 수 있습니다. 이는 나중에 각 요청을 인증하는 데도 헤더가 사용되므로 일관성을 유지하기 위한 것입니다.

앱으로 이동합니다.post('/세션', 함수(req, res, 다음) {
만약 (!req.body.사용자 이름) {
반환 res.보내기(400, '사용자 아이디를 지정해야 합니다');
}
만약 (!req.body.비밀번호) {
반환 res.보내기(400, '비밀번호를 지정해야 합니다');
}계정 모델.사용자 이름(req.body.사용자 이름, 함수(err, 사용자) {
만약 (err) {
반환 다음(err);
}

만약 (크립트.sha1(req.body.비밀번호) !== 사용자.비밀번호) {
반환 res.보내기(400, '비밀번호가 일치하지 않습니다');
}

세션 모델.create(사용자.uid, 함수(err, 세션) {
만약 (err) {
반환 다음(err);
}

res.setHeader('권한 부여', '무기명 ' + 세션.sid);

// 보안상의 이유로 비밀번호 삭제
삭제 사용자.비밀번호;
res.보내기(사용자);
});
});
});

이제 세션 생성이 완료되었으므로 요청별로 사용자를 인증하는 메서드를 추가해 보겠습니다. 이렇게 하면 사용자 세션을 확인할 수 있는데, 지금은 app.js에 넣었지만 나중에 경로가 별도의 파일로 분리되기 시작하면 별도의 파일에 넣는 것이 더 나을 수 있습니다. 사용자를 인증하기 위해 표준 HTTP 권한 부여 헤더를 확인하여 세션 ID를 가져온 다음 세션 모델을 사용하여 이를 조회합니다. 모든 것이 계획대로 진행되면 새로 찾은 사용자 ID를 나중에 라우트 핸들러를 위한 요청에 저장합니다.

함수 authUser(req, res, 다음) {
req.uid = null;
만약 (req.헤더.권한 부여) {
var authInfo = req.헤더.권한 부여.분할(‘ ‘);
만약 (authInfo[0] === '무기명') {
var sid = authInfo[1];
세션 모델.get(sid, 함수(err, uid) {
만약 (err) {
다음('세션 ID가 유효하지 않습니다');
} else {
req.uid = uid;
다음();
}
});
} else {
다음('이 엔드포인트에 액세스할 수 있는 권한이 있어야 합니다');
}
} else {
다음('이 엔드포인트에 액세스할 수 있는 권한이 있어야 합니다');
}
}

주로 authUser 메서드가 작동하는 모습을 보여주기 위한 목적으로 저는 /me 엔드포인트를 호출하여 사용자 문서를 반환합니다. authInfo 핸들러가 요청에 저장한 UID를 기반으로 계정 모델을 조회한 후 이를 클라이언트에게 반환하고, 물론 비밀번호를 먼저 제거합니다.

앱으로 이동합니다.get('/me', authUser, 함수(req, res, 다음) {
계정 모델.get(req.uid, 함수(err, 사용자) {
만약 (err) {
반환 다음(err);
}삭제 사용자.비밀번호;
res.보내기(사용자);
});
});

피날레

이제 계정을 만들고, 로그인하고, 사용자에 대한 저장된 정보를 요청할 수 있어야 합니다. 다음은 파트 1에서 만든 사용자를 사용한 예제입니다.

> POST /세션
{
"username": "brett19",
"비밀번호": "성공!"
}
< 200 미만 확인
헤더(권한): Bearer 0e9dd36c-5e2c-4f0e-9c2c-bffeea72d4f7> GET /me
헤더(권한): Bearer 0e9dd36c-5e2c-4f0e-9c2c-bffeea72d4f7
< 200 미만 확인
{
"uid": “b836d211-425c-47de-9faf-5d0adc078edc”,
"이름": "브렛 로슨",
"username": "brett19"
}

이 애플리케이션의 전체 소스는 여기에서 확인할 수 있습니다: https://github.com/brett19/node-gameapi

즐겨보세요! Brett

이 문서 공유하기
받은 편지함에서 카우치베이스 블로그 업데이트 받기
이 필드는 필수 입력 사항입니다.

작성자

게시자 브렛 로슨, 수석 소프트웨어 엔지니어, Couchbase

브렛 로슨은 카우치베이스의 수석 소프트웨어 엔지니어입니다. Brett은 Couchbase Node.js 및 PHP 클라이언트의 설계와 개발을 담당하고 있으며, C 라이브러리인 libcouchbase의 설계와 개발에도 참여하고 있습니다.

댓글 하나

  1. 공유해 주셔서 감사합니다. 이 시리즈는 한동안 재미있을 것 같습니다!

    SessionModel.get 함수에 관한 질문입니다: 조회 시 세션 문서의 만료를 3600으로 업데이트해야 하나요? 그렇지 않으면 로그인하면 해당 시간 내에 활동했더라도 한 시간 후에 세션이 자동으로 종료됩니다.

    다음 시리즈도 기대해 주세요.

    1. 안녕하세요, 브렌든! 완전히 맞습니다! 인증 사용자 함수 내에서 세션을 터치하여 만료 시간을 갱신해야 합니다. 다음 파트에 이 부분을 추가하겠습니다.

      건배, 브렛

      1. 세션 모델 내에서 기존 db.get 호출에 {expiry: 3600}을 SessionModel.get 내의 기존 db.get 호출에 추가하면 됩니다.

        멋진 시리즈였고 유니버스 글도 기대됩니다.

        1. 벤, 어떤 유니버스 글을 말씀하시는 건가요?

  2. 실리에 자비에 10월 8, 2013에서 6:31 오후

    글을 보내주셔서 감사합니다. '참조'에 대해 질문이 있습니다.
    문서\'. \'보기\' 대신 \'참조 문서\'를 사용한 이유

    1. 이 경우 참조 문서를 사용하기로 한 이유는 데이터베이스 일관성을 보장하기 위해서였습니다. 뷰는 인덱스 업데이트가 지연되므로 사용자가 특정 사용자명을 등록한 후 뷰가 다시 색인되기 전에 다른 사용자가 동일한 사용자명을 등록할 수 있습니다(뷰에서 해당 사용자명이 아직 사용 중인 것으로 반환하지 않기 때문에). 참조 문서를 사용하면 이러한 지연이 발생하지 않으므로 일관성을 보장할 수 있습니다.

      건배, 브렛

  3. "최종 계정 모델 js\"에 대한 참조는 \"최종 세션 모델 js\"로 표시되어야 합니다.

    또한 AccountModel.get()에 대한 스니펫 사본도 포함할 수 있습니다.

    authUser 함수를 구현하는 데 있어서는 다음과 같이 자체 파일 \'lib/httpauth.js\'에 넣었습니다:

    그런 다음 경로에 전달하기만 하면 됩니다:

    1. 감사합니다 rdev5! 제안하신 대로 텍스트를 수정했습니다.

  4. 헤더 권한이 있는 포스트맨에서 엔드포인트를 테스트할 수 없습니다.

    POST /세션

댓글 남기기

카우치베이스 카펠라를 시작할 준비가 되셨나요?

구축 시작

개발자 포털에서 NoSQL을 살펴보고, 리소스를 찾아보고, 튜토리얼을 시작하세요.

카펠라 무료 사용

클릭 몇 번으로 Couchbase를 직접 체험해 보세요. Capella DBaaS는 가장 쉽고 빠르게 시작할 수 있는 방법입니다.

연락하기

카우치베이스 제품에 대해 자세히 알고 싶으신가요? 저희가 도와드리겠습니다.