개발자가 되고 싶은 사람

Passport.js 타사 로그인인증 구현(facebook)

|

Passport.js를 통해 타사 로그인 인증 구현(facebook).

  • Passport.js 패키지를 사용해 로그인을 구현하도록 한다. passport는 한글로 여권이다. 이름처럼 자신의 웹사이트에 방문할때 여권같은 역할을 함으로서 로그인을 쉽게 할 수 있게 도와준다.
  • passport.js 를 사용하면 타사인증을 훨씬 쉽게 할 수 있다.
  • 로그인 하는 사용자 정보를 직접저장하지 않고, 네이버/카카오/페북 등을 통해 확인하는 절자를 거쳐서 로그인을 한다.
  • passport.js를 사용함으로서 사용자 정보를 저장할 필요가 없기 때문에 안전하고, 회원가입을 훨씬 더 간단히 처리할 수 있는 장점이있다.
  • express-session은 passport로 로그인 후 유저 정보를 세션에 저장하기 위해 사용한다.
  • 나같은 경우, 타사 로그인 인증 대상으로 facebook을 선택했다.

1. facebook app을 설정

  • facebook developer로 접속하여 프로젝트를 생성한다.
  • myapp 등록을 통해 facebook을 이용하는 일종의 응용app으로 설정된다고 한다…

2. myapp에 로그인 인증을 사용하기 위한 설정을 한다.

3. passport.js - facebook설정을 위해 홈페이지를 참고

  • https://github.com/jaredhanson/passport-facebook#readme 에 들어가서 facebook 로그인을 설정하기 참고.
  • passportjs.org

4. 필수 npm 설치하기

npm install passport-facebook

5. facebook strategy를 설정

  • local strategy아래에(기존에 설정했던) facebook strategy를 설정한다.
var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;
  • facebook전략으로 동작하도록 설정.
app.get(
   '/auth/facebook', 
   passport.authenticate( //facebook전략으로 동작
     'facebook',
     { scope: 'email' } //scope을 추가해서 페북에서 한번더 확인해야 한다.
    ),
);

facebook Strategy 설정 중.. 타사인증에 라우터 두개 필요, why??

1번째 과정 : 페북과 내 app이 왕복(passport가 facebook으로 redirect시켜줌.)
2번째 과정 : fb에서 몇개의 정보를 붙여서 callback으로 처리. (서로 확인하는 과정이 내제되어 있음.)

  • 로그인 후, 크롬 네트워크탭에서 확인 가능하다.
    이미지

  • 타사인증은 보안적으로 위험한일. 이 과정에서 각각의 서비스가 맞는지 코드내에서 상호간의 검증과정이 일어난다.

구현 중 어려웠던 점.

  • 나의 경우 /auth/facebook/callback 이렇게만 썼더니 에러들이 발생했다.

  • facebook에서 새 app등록하고, 도메인 http://localhost:3003 로컬로 등록한 뒤 로그인 테스트하면 아래와 같은 에러가 떴다.

1)

Insecure Login Blocked: You can’t get an access token or log in to this app from an insecure page. Try re-loading the page as https:// at Strategy.authenticate

2)

차단된 URL: 리디렉션 URI가 앱의 클라이언트 OAuth 설정의 화이트리스트에 없으므로 리디렉션하지 못했습니다. 클라이언트 및 웹 OAuth 로그인이 설정되었는지 확인하고 모든 앱 도메인을 유효한 OAuth 리디렉션 URI로 추가하세요.

  • 구글링해봤더니 facebook자체에서 https만 지원한다고 정책규정이 바껴서 https밖에 못쓴다. 설정바꾸는게 있다는데..도저히 못찾겠어서 포기하고 openSSL에서 Key값을 받아 설정하고 포트를 두개 설정하여 https서버를 제공해주는 방법으로 해결해보았다..ㅠ (https://localhost:443/auth/facebook/callback 이거 아예 강제맵핑했다.)

( 참고 : https://stackoverflow.com/questions/49376862/insecure-login-blocked-try-re-loading-the-page-as-https-im-using-oneall-s All new apps created as of March 2018 have this setting on by default and you should plan to migrate any existing apps to use only HTTPS URLs by March 2019.” Plus, Chrome is planning to mark all non-HTTPS sites as “insecure” soon (Chrome 68, will be released in July 2018), so you might want to switch to HTTPS rather sooner than later. )

구현

passport.use(new FacebookStrategy({
  clientID: 'clientID설정!!',
  clientSecret: 'clientSecret설정!!', //절대 노출되면 안되는 정보.
  callbackURL: "https://localhost:443/auth/facebook/callback",  

  //profile로 받을 데이터들을 명시적으로 출력해줘야 한다.
  profileFields:['id', 'email', 'gender', 'link', 'name', 
  'timezone', 'updated_time', 'verified', 'displayName' ]
  //email안던져 준다........
},

//callback받는것
//profile, done이 중요하다.
function(accessToken, refreshToken, profile, done) {
  console.log(profile); //여기서 id는 facebook식별자
  var authId = 'facebook:'+profile.Id;
  for(var i=0; i<users.length; i++){
    var user = users[i];
    if(user.authId === authId){ //facebook으로 로긴했을땐 authid가 있을 것.
      return done(null, user);
    } 
  }
  var newuser = {
    'authId':authId,
    'displayName' :profile.displayName
    /* 'email' : profile.emails[0].value 필드에서 email값을 안뿌려주나보다. Cannot read property '0' of undefined 에러떠서 주석처리함.*/
  }
  users.push(newuser);
  done(null, newuser)
  //이거 다음에 seriallizeUser
  // User.findOrCreate(..., function(err, user) {
  //   if (err) { return done(err); }
  //   done(null, user);
  // });
 }
));
  • deserialize확인
  • session은 파일에 저장되고 있음.
  • 배열 - 메모리성에 저장되어있는건 사라짐.
  • session은 살아있는데, 배열에 있는건 메모리상에 있어서 사라진상태.
  • 이 문제를 완화시키기 위해 done(‘there is no user..’) session에 저장된것 처럼 어딘가 저장을 해놓고 있어야 함.
  • 무한히 대기하고 있으면 session을 지워야함.
  • 원하는 정보를 scope에 저장해놓고 있어야 함.

  • 그러면 아래와 같이 callback data가 던져진다. 이런식으로
{ id: 'id',
  username: undefined,
  displayName: 'displayName~',
  name: { familyName: 'hi', givenName: 'hi', middleName: undefined },
  gender: 'female',
  profileUrl: 'https://www.facebook.com/app_scoped_user_id/~/',
  provider: 'facebook',
  _json:
   { id: 'id값',
     link: 'https://www.facebook.com/app_scoped_user_id/227712857970634/',
     timezone: 9,
     verified: true,
     name: '~' } }
serializeUser { authId: 'facebook:undefined', displayName: '~' }

think 차이점..

  1. session, passport.js 로 face북 로그인 인증 –> 개인스터디
  2. cookie로 로그인 구현 –> 실제어플/프로젝트
  3. 이메일 확인, 비밀번호 인증.

출처

게시글 좋아요 기능 처리

|

Node AWS S3 스토리지 이용하여 파일업로드 구현

|

Amazon S3란??

  • Amazon S3 (Simple Storage Service) : AWS S3 (Simple Stoage Service)는 파일을 저장하기 위한 스토리지이다. 일반적인 파일시스템의 개념과는 약간 다르고, 파일 이름을 대표하는 key와 파일 자체로 구분되는 Object Storage이다.
  • 용량 저장할 수 있는 파일의 크기는 개당 1byte~5TB이고, 총 저장 용량에는 제한이 없다. 디렉토리와 비슷한 개념으로, bucket이라는 개념을 가지고 있다.
  • S3에는 파일을 업로드 할때, multipart uploading이라는 기능을 제공한다. 이 multipart 업로드 기능을 구현은 SDK 형태로 재공되는 라이브러리를 사용하면 된다. 사용법이 매우 간단한 서비스이기는 하지만, 쉽고 안전하며, 데이터 저장소로써 필수적인 기능등은 거의 다 갖추고 있으며, 저렴한 가격에, 다른 서비스와 연계성이 높다.
  • 단, S3를 이용한 시스템 설계시에는 일반 파일 시스템과 같은 형태로 접근을 하면 안된다. HTTP 형식으로 접근을 하기 때문에, 성능이 그만큼 나오지 않는다. 이 부분에 대한 고려만 충분히 하면 AWS에서 EC2 다음으로 가장 가치가 높은 서비스가 아닐까 싶다. 출처:조대협의 블로그
  • S3에서는 파일을 업로드 할시 PUT이라 함.

구현

작동원리

fileupload시, aws에서 사용가능한 s3스토리지에서 저장을 하게 됩니다. 그리고 버킷에 저장하는 multipaty 모듈의 기능을 이용하여 스토리지에 버킷에 저장하게 됩니다.

필요한 것

  • Credential 자격증명 (aws에서 인증을 받아야 AWS 인프라를 사용할 수 있다.)

필수패키지 install

  • formidable 파일 업로드를 위한 모듈
  • async순차 실행을 위한 모듈
  • aws-sdk S3 업로드를 위한 모듈, 해당 모듈은 AWS 서비스를 사용하기 위한 javascript 객체를 제공
  • multiparty : 파일업로드를 위한 npm 모듈
npm install formidable --save
npm install async --save
npm install aws-sdk --save
npm install multiparty --save

S3의 스토리지 인프라에 접근하기 위한 AWS 인증 Credential 파일 설정

  • 어플리케이션구동시 해당 credential 파일을 읽도록 설정한다. AWS.config.loadFromPath();

HTML Form

<div class="form-group">
    <label for="contents">표지이미지</label>
    <input  type="file" id="img_files" name="img_files" accept="image/*" class="form-control" onchange="__common.cmdSendByAjax(this.id, 'frm');">
    <input type="hidden" id="Key" name="Key" />
    <input type="hidden" id="Location" name="Location" />
</div>

action을 수행하는 script파일.

__common = {
    cmdSendByAjax : function (_obj, _form) {
    $('form').ajaxForm({
        url: '/util/FileUpload',
        enctype: 'multipart/form-data',

        beforeSubmit: function (data, form, option) {
            console.info('beforeSubmit');
            console.info(data);
            console.info(form);
            console.info('beforeSubmit');
            //validation체크
            //막기위해서는 return false를 잡아주면됨
            return true;
        },
        success: function (response, status) {
            console.info(response);
            $('#Location').val(response.Location);
        },
        error: function () {
            console.info('error');
        }
    });
    $('#' + _form).submit();
}
}

router의 util.js

  • aws기본 설정
AWS.config.loadFromPath('./config/config.json');
AWS.config.region = 'ap-northeast-2'; //지역 설정
  • AWS fileUpload 설정!!
router.all('/FileUpload', function(req, res, next) {
var form = new multiparty.Form();

form.on('field',function(name,value){
    console.log('normal field / name = '+name+' , value = '+value);
});

//S3 버킷 설정
form.on('file',function(name, file){
    var param = {
        'Bucket':'BucketName', //s3에 설정한 버킷 이름 설정.
        'Key':file.originalFilename,
        'ACL':'public-read',
        'Body':null
    };

    var orgFileName = file.originalFilename;
    var orgFileExtension = orgFileName.lastIndexOf(".");
    
    //원본 파일명을 바꾸기 위한 코드.
    //파일명의 중복을 막기 위해 설정
    function makeid() {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        
        for (var i = 0; i < 10; i++)
        text += possible.charAt(Math.floor(Math.random() * possible.length));
        
        return text;
    }

    param.Key = makeid() + orgFileExtension;

    //param.Key = file.originalFilename;
    param.Body = require('fs').createReadStream(file.path);
    
    //upload
    s3.upload(param, function(err, data){
        console.log(err);
        console.log(data);

        res.status(200).send(data);
    });
    
});

form.parse(req);
});

var param = {...} 객체는 AWS S3업로드에 대한 정보

  • Bucket : S3 Bucket 이름을 지정합니다.
  • Key : S3의 경로 및 파일 이름을 지정합니다.
  • ACL : 파일 권한에 대한 설정입니다.
  • Body : 업로드할 파일의 경로를 지정합니다.

출처

Node.js firebase를 사용하여 fcm처리

|

fcm이란? fcm으로 push알람 구현

  • 관리자단에서 푸시알람 구현하기 위해 firebase를 사용하여 처리하기로 했다. (firebase Cloud Messaging)으로 푸시기능 구현

  • FCM은 Firebase Cloud Messaging으로 메시지와 알림을 무료로 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션이다. 어플리케이션의 알림기능을 구현하기 위해선, 현재의 서버에서 클라이언트로 알림을 전달할 방법이 있어야 한다. 하지만, 본연의 서버의 기능을 수행하면서 또 하나의 복잡한 알림 기능까지 포함하면 서버의 속도는 처리량이 많아 느려질 것이다. 따라서 이러한 해결책으로 알림의 기능은 다른 서버가 제공해주고 본 서버는 알림 기능을 제공하는 서버에 알림이 있는지 요청을 해서 정보를 가져오는 구조이다. 알림을 제공해주는 곳이 바로 구글이 된다. 구글 firebase홈페이지에서 FCM기능을 더 자세히 확인해볼 수 있다.

  • 작동원리 이미지

  • FCM 구현에는 송수신을 위한 두 가지 주요 구성요소가 포함된다.

    1. Firebase용 Cloud 함수 또는 앱 서버와 같이 메시지를 작성, 타겟팅, 전송할 수 있는 신뢰할 수 있는 환경.
    2. 메시지를 수신하는 iOS, Android 또는 웹(자바스크립트) 클라이언트 앱.
      Admin SDK 또는 HTTP 및 XMPP API를 통해 메시지를 보낼 수 있습니다. 강력한 타겟팅 및 분석 기능이 기본적으로 포함된 알림 작성기를 사용하여 마케팅 또는 참여 유도 메시지를 테스트 또는 전송할 수도 있습니다.

내가 해야할 관리자단 작업

  • fcm-node 모듈을 통한, fcm서버로 알림전송 부분 소스 개발.
  • fcm접속을 위한 서버키 확보.
  • 사용자테이블에 device token값만 받으면 되는 상황.

작업절차

  1. firebase에 프로젝트를 설정한다.
  2. 서버key값을 받고 프로젝트에 설정한다.
  3. device token값을 받는다.
  4. 코드에 message를 설정한다(notification).
  5. 전송 test 및 확인.

필수 패키지 설치

var FCM = require('fcm-node');

소스

1. push를 처리하는 router

/**
* 푸시 발송 by pushBoardId
* @route {POST} /pushMng/pushSend
*/
router.all('/pushSend', function(req, res){
var __title = '';
var __contents = '';

pushMngDb.selectPushMngListOne(req.body, function (err, rows) {
    __title = rows[0].title;
    __contents = rows[0].contents;
    console.log('selectPushMngListOne');

    memberMngDb.selectMemberPushYn(function(err, rows){
        console.log('selectMemberPushYn');
            
        for(var i =0; i < rows.length ; i++){
            if(rows[i].pushsendYn == 'Y'){
                var _temp = new Object();
                _temp.deviceToken = rows[i].deviceToken;
                _temp.title = __title;
                _temp.contents = __contents;
                _fcm.Send(_temp);
            }
        }
    });
});
    res.send();
});

2. util에서 fcm처리

var FCM = require('fcm-node');
var serverKey = require('ket값을 가져오는 경로');

var fcm = new FCM(serverKey);

module.exports = new function () {
    this.Send = function (query, callback) {
    /** Firebase(구글 개발자 사이트)에서 발급받은 서버키 */
    // 가급적 이 값은 별도의 설정파일로 분리하는 것이 좋다.
    
    /** var client_token = 안드로이드 단말에서 추출한 token값 */

    /** 푸시메시지 발송절차 */
    //var fcm = new FCM(serverKey);
    console.log('===========================');
    console.log(query);
    var client_token = query.deviceToken; 
    var title = query.title;
    var body = query.contents;

    //발송 후 시간 리턴(db에 timestamp 처리하기)

    /** 발송할 Push 메시지 내용 */
    var push_data = {

    // 수신대상
    to: client_token,
    // App이 실행중이지 않을 때 상태바 알림으로 등록할 내용
    notification: {
        title: title,
        body: body,
        sound: "default",
        click_action: "FCM_PLUGIN_ACTIVITY",
        icon: "fcm_push_icon"
    }
    };

    fcm.send(push_data, function(err, response) {
    if (err) {
        console.error('Push메시지 발송에 실패했습니다.');
        console.error(err);
        return;
    }

    console.log('Push메시지가 발송되었습니다.');
    console.log(response);
    console.log(response.results);
    });

    }
}

}

출처

Rest API란?

|

Rest API란?

  • REST : Representational State Transfer

    표면적인 실체는 ‘스타일’또는 ‘패턴’이라고 할 수 있다. 많은 정보, 리소스들을 어떻게 전달할 것인가?) 기존방식보다 데이터를 주고 받을때 더 낫게 하도록… 일관성 있는 웹서비스 인터페이스 설계를 위해 사용.

  • 주로 비동기로 데이터를 주고받을때 API를 주고 받을 때 정해진 규칙하에 전달하는 것.
  • 웹을 근간으로 하는 http protocol 기반이다.
  • 리소스(자원)은 URI(Uniform Resource Identifiers)로 표현하며 말 그대로 ‘고유’해야 한다.
  • URI는 단순하고 직관적인 구조이어야 한다.
  • xml/json을 활용해서 데이터를 전송한다. (주로json- 보다 직관적)
  • REST 개념은 서버와 클라가 완전히 분리된 개념이다.(일반적으로 웹개발을 할때 서버언어와 클라이언트 언어랑 같이 사용해서 이부분이 헤깔렸다.)
  • REST하게 클라이언트랑 서버간에 데이터를 주고 받는 방식
  • 프로젝트시 내가 한 작업은 철저히 서버단 작업이다. node로 개발하면서 클라이언트에 response해주는 인터페이스를 rest라고 보면 된다.
  • 클라이언트는 앵글러나 리엑트로 작업해서 rest를 호출하여 데이터를 받는 것이다. 내가 한 작업에 빗대어 생각해보면 페이지에서 $.ajax로 url주소로 데이터를 호출하는 부분이 있다.(jQuery로 서버데이터 호출하는 부분.) jQuery로 호출하는 url주소들을 내가 만들었던 것.

  • 대충 요런식..
  cmdLoadByAjax : function (url, params, method) {
  var request = \$.ajax({
  type: !method ? 'POST' : method,
  url: url,
  contentType: 'application/json; charset=utf-8',
  data: params,
  dataType: 'json',
  async: true,
  cache: false
  });
  return request;
  }
  

1. REST API의 탄생

  • REST는 Representational State Transfer라는 용어의 약자로 2000년도에 로이 필딩 (Roy Fielding)의 박사학위 논문에서 최초로 소개. 로이 필딩은 HTTP의 주요 저자 중 한 사람으로 웹의 장점을 최대한 활용할 수 있는 아키텍처로써 REST를 발표

2. REST 구성

자원(Resource) - URI
행위(Verb) - HTTP METHOD
표현(Representations)

3. REST 의 특징

1) Uniform (유니폼 인터페이스)

Uniform Interface는 URI로 지정한 리소스에 대한 조작을 통일되고 한정적인 인터페이스로 수행하는 아키텍처 스타일을 말합니다.

2) Stateless (무상태성)

REST는 무상태성 성격을 갖습니다. 다시 말해 작업을 위한 상태정보를 따로 저장하고 관리하지 않습니다. 세션 정보나 쿠키정보를 별도로 저장하고 관리하지 않기 때문에 API 서버는 들어오는 요청만을 단순히 처리하면 됩니다. 때문에 서비스의 자유도가 높아지고 서버에서 불필요한 정보를 관리하지 않음으로써 구현이 단순해집니다.

3) Cacheable (캐시 가능)

REST의 가장 큰 특징 중 하나는 HTTP라는 기존 웹표준을 그대로 사용하기 때문에, 웹에서 사용하는 기존 인프라를 그대로 활용이 가능합니다. 따라서 HTTP가 가진 캐싱 기능이 적용 가능합니다. HTTP 프로토콜 표준에서 사용하는 Last-Modified태그나 E-Tag를 이용하면 캐싱 구현이 가능합니다.

4) Self-descriptiveness (자체 표현 구조)

REST의 또 다른 큰 특징 중 하나는 REST API 메시지만 보고도 이를 쉽게 이해 할 수 있는 자체 표현 구조로 되어 있다는 것입니다.

5) Client - Server 구조

REST 서버는 API 제공, 클라이언트는 사용자 인증이나 컨텍스트(세션, 로그인 정보)등을 직접 관리하는 구조로 각각의 역할이 확실히 구분되기 때문에 클라이언트와 서버에서 개발해야 할 내용이 명확해지고 서로간 의존성이 줄어들게 됩니다.

6) 계층형 구조

REST 서버는 다중 계층으로 구성될 수 있으며 보안, 로드 밸런싱, 암호화 계층을 추가해 구조상의 유연성을 둘 수 있고 PROXY, 게이트웨이 같은 네트워크 기반의 중간매체를 사용할 수 있게 합니다.

4. REST API 디자인 가이드(중요)

  • REST API 설계 시 가장 중요한 항목
    1. URI는 정보의 자원을 표시
    2. 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.

4-1. REST API 중심 규칙

  1. URI는 정보의 자원을 표현해야 한다. (리소스명은 동사보다는 명사를 사용)
    GET /members/delete/1 (x)
  • 위와 같은 방식은 REST를 제대로 적용하지 않은 URI입니다. URI는 자원을 표현하는데 중점을 두어야 합니다. delete와 같은 행위에 대한 표현이 들어가서는 안된다.
  1. 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE 등)로 표현
    위의 잘못 된 URI를 HTTP Method를 통해 수정해 보면 아래와 같다.
    DELETE /members/1
  • 회원정보를 가져올 때는 GET, 회원 추가 시의 행위를 표현하고자 할 때는 POST METHOD를 사용하여 표현합니다.
  //회원정보를 가져오는 URI
  GET /members/show/1 (x)
  GET /members/1 (o)
  
//회원을 추가할 때
GET /members/insert/2 (x) - GET 메서드는 리소스 생성에 맞지 않다.
POST /members/2 (o)

HTTP METHOD의 알맞은 역할

  • POST, GET, PUT, DELETE 이 4가지의 Method를 가지고 CRUD를 할 수 있다.
METHOD 역할
POST POST를 통해 해당 URI를 요청하면 리소스를 생성한다.
GET GET을 통해 해당 리소스를 조회, 리소스를 조회하고 해당 도큐먼트에 대한 자세한 정보를 가져온다.
PUT PUT을 통해 해당 리소스를 수정
DELETE DELETE를 통해 리소스를 삭제

API Design

  • 복수명사를 사용(/movies)
  • 필요하면 URL에 하위 자원을 표현. (/movies/23)
  • 필터조건을 허용할 수 있음 (/movies?state=active)

  • ex)
URL Methods 설명
/movies GET 모든 영화리스트 가져오기
/movies POST 영화 추가
/movies/:title GET title 해당 영화 가져오기
/movies/:title DELETE title 해당 영화 삭제
/movies/:title PUT title 해당 영화 업데이트
/movies?min=9 GET 상영중인 영화리스트

URI, URL의 차이?!

  • URL : Uniform Resource Locator, 정형화 된 리소스 위치표시
  • URI : Uniform Resource Identifier
    예전에는 URL이 가리키는게 파일리소스 였는데 요즘은 Rewrite 등의 Apache , IIS, Tomcat 핸들러 때문에 자원 이라고 부른다.
  • URL 은 다음과 같다.
    http://test.com/work/sample.pdf
    test.com 서버에서 work 폴더안의 sample.pdf 를 요청하는 URL.
  • URI(통합 자원 식별자) 의 예는 다음과 같다.
    REST 서비스 (url로 실행되는 서비스)
    http://service.com/tv/turn/on

  • URI(동물) 가 좀더 상위 개념이라서 URL(강아지), URN(다람쥐) 등의 하위 개념을 포함한다.
  • 모든 URL 는 URI 이다. 가 성립힌다. (TRUE), URI = URL + URN

출처