블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

빠르게 훝어보는 node.js

#16  - Passport를 이용한 OAuth 2.0 API 인증 (Facebook 2/2)

조대협 (http://bcho.tistory.com)


node.js에서 페이스북 로그인하기

앞에서(http://bcho.tistory.com/913) 설명한 Facebook 로그인 시나리오를 기반으로 하여 node.js passport-facebook 모듈을 이용해서, 간단한 로그인 서비스를 만들어보자.

시나리오

만들고자 하는 시나리오는 다음과 같다.



/login 페이지에서 페이스북 로그인 버튼이 출력된다.



페이스북 로그인 버튼을 누르면 /auth/facebook 페이지로 이동한다.

/auth/facebook 페이지에서는 페이스북에 로그인을 하기 위한 OAuth 요청을 passport-facebook 모듈을 이용해서 만든후, 페이스북 로그인 페이지로 Oauth 요청과 함께 redirect한다.



페이스북 로그인 페이지에서 계정과 비밀번호를 입력하면 해당 앱에 대한 권한을 허용할것인지를 페이스북이 물어본다.



확인을 누르면, 로그인이 성공하고, 로그인 결과를 /auth/facebook/callback으로 redirect한다.

페이지에서는 페이스북에서 전달해온 사용자 정보를 받아서, node.js 세션에 저장한다. 후에, /login_success 페이지로 이동한다.

/login_success 페이지에서는 세션에 저장된 페이스북 사용자 정보를 JSON 문서 형태로 출력한다.



이것이 우리가 지금부터 구현하고자 하는 흐름이다.

2. passport-facebook 모듈을 이용하여 구현하기

그러면 node.js 이용해서 구현을 해보자.

개발환경에서,

Ÿ   npm –g install passport

Ÿ   npm –g install passport-facebook

명령을 이용하여 passport passport-facebook 모듈을 설치한다.



다음으로 https://developers.facebook.com 로그인해서새로운 앱을 등록한다.



Settins 메뉴로 이동하여 +Add Platform 버튼을 클릭한다여기서는 웹사이트를 Facebook 계정으로 연동할 예정이기 때문에, WebSite 선택한다.



 

WebSite 플랫폼을 추가하면 아래와 같이 WebSite 메뉴가 생성되고, Site URL 넣을 있는 페이지가 나온다여기서는 개인 노트북 개발환경에서 node.js 띄워놓고 개발할것이기 때문에로컬 사이트의 URL 적어 놓자. 예제에서는 노트북에서 3000 포트에 node.js 띄울 것이기 때문에, URL 아래와 같이 “http://localhost:3000”으로 적어놓는다.



다음으로 코드를 구현한다.

Express 애플리케이션을 생성한다. ( 예제는 3.x버전을 기준으로 하고 있다.)

다음으로 /public 디렉토리에 index.html 다음과 같이 구현한다.

<!DOCTYPE html>

<html>

<head>

    <title></title>

</head>

<body>

 <button onclick="location.href='/auth/facebook'">Facebook login</button>

</body>

</html>

 

다음으로 코드를 구현해보자. 아래는 /app.js 소스코드이다.

var express = require('express');

var routes = require('./routes');

var user = require('./routes/user');

var http = require('http');

var path = require('path');

 

var passport = require('passport')

    , FacebookStrategy = require('passport-facebook').Strategy;

 

// serialize

// 인증후 사용자 정보를 세션에 저장

passport.serializeUser(function(user, done) {

    console.log('serialize');

    done(null, user);

});

 

 

// deserialize

// 인증후, 사용자 정보를 세션에서 읽어서 request.user 저장

passport.deserializeUser(function(user, done) {

    //findById(id, function (err, user) {

    console.log('deserialize');

    done(null, user);

    //});

});

 

passport.use(new FacebookStrategy({

        clientID: ' 페이스북 개발자 사이트에서 찾아서 넣으세요 ',

        clientSecret: '페이스북 개발자 사이트에서 찾아서 넣으세요',

        callbackURL: "http://localhost:3000/auth/facebook/callback"

    },

    function(accessToken, refreshToken, profile, done) {

        console.log(profile);

        done(null,profile);

    }

));

var app = express();

 

// all environments

app.set('port', process.env.PORT || 3000);

app.set('views', path.join(__dirname, 'views'));

app.set('view engine', 'ejs');

app.use(express.favicon());

app.use(express.logger('dev'));

app.use(express.json());

app.use(express.urlencoded());

app.use(express.methodOverride());

app.use(express.session({ secret: 'your secret here' }));

app.use(passport.initialize());

app.use(passport.session());

app.use(app.router);

app.use(express.static(path.join(__dirname, 'public')));

 

// development only

if ('development' == app.get('env')) {

  app.use(express.errorHandler());

}

 

app.get('/auth/facebook', passport.authenticate('facebook'));

app.get('/auth/facebook/callback',

    passport.authenticate('facebook', { successRedirect: '/login_success',

        failureRedirect: '/login_fail' }));

app.get('/login_success', ensureAuthenticated, function(req, res){

    res.send(req.user);

});

app.get('/logout', function(req, res){

    req.logout();

    res.redirect('/');

});

function ensureAuthenticated(req, res, next) {

    // 로그인이 되어 있으면, 다음 파이프라인으로 진행

    if (req.isAuthenticated()) { return next(); }

    // 로그인이 안되어 있으면, login 페이지로 진행

    res.redirect('/');

}

 

http.createServer(app).listen(app.get('port'), function(){

  console.log('Express server listening on port ' + app.get('port'));

});

 

 

Passport  passport-facebook 이용하기 위해서 다음과 같이 모듈을 import한다.

var passport = require('passport')

    , FacebookStrategy = require('passport-facebook').Strategy;

 

다음으로 이전 글의 LocalStrategy (http://bcho.tistory.com/920 참고) 에서 했던것 처럼, passport serializer deseriazlier 구현한다.

 

// serialize

// 인증후 사용자 정보를 세션에 저장

passport.serializeUser(function(user, done) {

    console.log('serialize');

    done(null, user);

});

 

 

// deserialize

// 인증후, 사용자 정보를 세션에서 읽어서 request.user 저장

passport.deserializeUser(function(user, done) {

    //findById(id, function (err, user) {

    console.log('deserialize');

    done(null, user);

    //});

});

 

Serailizer LocalStrategy 예제 부분에서도 언급했지만, 로그인이 성공하였을 , 인증 후에, 세션에 사용자 정보를 저장하는 기능을 한다. 로그인 성공후 값이 user라는 인자를 통해서 전달되는데, 값을 done(null,user) 넣으면 HTTP session내에 저장된다. 예제에서는 페이스북에서 로그인 후에 넘어온 정보를 그대로 저장했지만 정보의 양이 많을 경우 메모리를 절약하기 위해서 사용자 정도만 세션에 저장하는 것이 좋다.

다음으로 Deserialize 매번 페이지 접근시 마다, 세션에 저장된 사용자 정보를 읽어서 HTTP request 객체에 user라는 객체를 추가로 넣어서 리턴한다.

이는 앞서 LocalStrategy 부분에서도 설명했지만, 세션에 많은 데이타를 넣게 되면 메모리 소모가 많기 때문에,세션에는 사용자 ID정도를 저장하고 상세 사용자 정보는 DB 별도의 외부 캐쉬 (Redis 같은) 곳에 정보를 저장해놓고 세션에 저장된 사용자 ID, 추가 사용자 정보를 읽어서, 정보를 user 객체에 넣어서 done(null,user) 호출해주면, request.user 통해서 값을 전달할 있다.

Passport Facebook Strategy 불러와서 정의한다.

passport.use(new FacebookStrategy({

        clientID: ' 페이스북 개발자 사이트에서 찾아서 넣으세요 ',

        clientSecret: ' 페이스북 개발자 사이트에서 찾아서 넣으세요 ',

        callbackURL: "http://localhost:3000/auth/facebook/callback"

    },

    function(accessToken, refreshToken, profile, done) {

        console.log(profile);

        done(null,profile);

    }

));

FacebookStrategy 정의할때 인자로 client ID,clientSecret 등을 넣어야 하는데, 값들은 페이스북 개발자 사이트에서 등록된 애플리케이션 정보를 보면 AppID App Secret이라는 값이 있는데,



App ID 값을 client ID, 그리고App Secret 값을 client Secret 입력하면 된다.

그리고, 코드내에서는 콜백함수를 정의하는데, 콜백함수에는 크게 4가지 인자가 전달된다.

Ÿ accessToken : OAuth accessToken이다. 토큰을 이용해서 필요하다면 페이스북의 오픈 API (REST API) 호출할 있다. 만약에 개발하고자하는 웹서비스가 페이스북 API 호출할 경우 accessToken session등에 저장해놓고 사용하기 바란다.

Ÿ   RefreshToken : OAuth refreshToken으로, accessToken 만료되었을 재발급을 요청하는 용도로 사용된다.

Ÿ   profile : 로그인한 페이스북의 사용자 정보를 리턴한다. 예제에서는 값을 모두 세션에 저장해놓고 사용했지만 앞에 seriazlier에서도 설명했듯이 가능하면 id 정도만 세션에 저장해서 메모리 사용량을 절약하기를 바란다.

다음으로 passport 사용하기 위해서 passport 미들웨어를 use 연결한다.

app.use(passport.initialize());

app.use(passport.session());

미들웨어는 HTTP 요청이 들어왔을때 정의된 순서에 따라서 순차적으로 실행되기 때문에 이때 순서에 주의하도록한다.

여기까지 준비되었으면, passport-facebook 모듈을 사용하기 위한 모든 준비가 되었다. 이제 페이지를 구현해보자

app.get('/auth/facebook', passport.authenticate('facebook'));

 

/auth/facebook 페이지에 대한 구현으로, 페이지에 들어오면 passport facebook strategy 모듈이 OAuth 요청을 만들어서 페이스북 로그인 페이지로 OAuth 요청을 Query String 실어서 redirect한다.

app.get('/auth/facebook/callback',

    passport.authenticate('facebook', { successRedirect: '/login_success',

        failureRedirect: '/login_fail' }));

 

다음으로 페이스북 로그인 페이지에서 로그인에 대한 결과를 처리하는 페이지로, /auth/facebook/callback 페이지로, 로그인 성공을 하면 /login_sucess 페이지로 리다이렉트하고, 실패하면 /login_fail 페이지로 리다리렉트 한다.

app.get('/login_success', ensureAuthenticated, function(req, res){

    res.send(req.user);

});

 

다음은 로그인 성공 페이지로, 로그인이 성공하면 세션에 저장된 페이스북 사용자 정보를 화면에 출력한다. 여기에 ensureAuthenticated라는 메서드를 중간에 인자로 넘겼는데, LocalStrategy 예제와 마찬가지로

function ensureAuthenticated(req, res, next) {

    // 로그인이 되어 있으면, 다음 파이프라인으로 진행

    if (req.isAuthenticated()) { return next(); }

    // 로그인이 안되어 있으면, login 페이지로 진행

    res.redirect('/');

}

 

인증이 되었는지 여부를 체크하고, 만약에 인증이 안되어 있다면 / 페이지로 리다이렉트 한다. 이는 로그인이 안된 상태에서 직접 /login_success 페이지로 접근할 경우, 다시 로그인 페이지를 보여주기 위함이다.

app.get('/logout', function(req, res){

    req.logout();

    res.redirect('/');

});

 

마지막으로 로그아웃페이지를 구현한다. /logout으로 들어가면 현재 로그인 되어 있는 세션을 로그아웃 처리한다. 이때 req.logout() 메서드를 호출하게 되면 passport에서 로그아웃 처리가 된다.

지금까지 간단하게나마, passport-facebook 모듈을 이용한 페이스북 로그인 서비스를 구현하였다. 빠르게 설명하기 위해서 가장 간단한 필수 기능만을 구현하였다 추가적인 사용정보를 받아들여서 저장하는 부분등은 직접 구현하기 바란다. J

예제에 대한 소스코드는

 https://github.com/bwcho75/node.js_study/tree/master/passport-facebook 있다. (모듈 디렉토리 제외 되어있음. app.js index.html 참고할것)

 

빠르게 훝어보는 node.js

#15  - Passport를 이용한 OAuth 2.0 API 인증 (Facebook 1/2)

조대협 (http://bcho.tistory.com)



REST 기반의 OPEN API 인증을 고민하다가 보니, 가장 많이 쓰이는게, OAuth 2.0이라서, OAuth 2.0을 보다보니, 도저히 이해가 안되겠다 싶어서, 간단하게 직접 구현해보기로 했다. OAuth 서버를 구현하기전에 먼저 테스트 클라이언트가 필요했기 때문에, node.js + passport 를 이용해서 facebook API를 호출하는 간단한 웹 사이트를 만들어보기로 했다.

Facebook API는 기본적으로 OAuth 2.0을 사용하고, Passport 모듈에서 잘 추상화된 라이브러리를 제공하기 때문에 쉽게 구축할 수 있으리라 생각했다. 실제로 구축해보니, 간단한 예제를 만드는데 약 1시간 30분 정도가 소요되었다.

자아 그러면 이제부터 Passport node.js를 이용해서 간단한 Facebook API를 호출하는 예제를 만들어보도록 하자.

OAuth 2.0

먼저 OAuth 2.0에 대한 개념을 알아보도록 하자. OAuth 2.0, 서비스에 대한 Authentication (인증) Authorization(권한 인가)에 대한 포괄적인 프레임웍이다. 특히 API 인증에 유용하게 사용될 수 있는데, 사용하는 애플리케이션의 타입(,모바일,자바스크립트)에 따라서 다양한 인증 메카니즘을 제공한다. 이를 grant type이라고 하는데, 대표적으로 4가지 형태의 grant type을 제공한다.

Ÿ   Authorization Code for webserver

Ÿ   Implicit for browser-based or mobile apps

Ÿ   Resource owner password credential for logging in with a username and password

Ÿ   Client credential for application access

다른 grant type에 대해서는 차차 소개하기로 하고, 오늘은 authorization code 방식의 grant type에 대해서 알아보도록 하자.

※ 다른 grant type에 대해서는 http://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified#web-server-apps 에 잘 정리가 되어 있다.

지금 구축하려고 하는 것은 facebook API를 내 서비스 웹서버에서 호출하는 시나리오이다.

Facebook API를 호출하기 위해서는 node.js 애플리케이션을 facebook 에 등록해야 한다. 이 등록은 facebook developer portal을 통해서 진행할 수 있는데, 아래 그림과 같이



1.  포탈에 node.js 사이트 관련 정보 (서비스 URL, CallBack URL, 서비스 명 등)을 입력하면,

2.  포탈에서 client_id client_secret을 발급해준다. client_id는 서버에서 발급되는 node.js 만든 서비스에 대한 id 라고 생각하면 되고, client_secret은 그 id에 대한 일종의 인증용 패스워드이다. (절대로 외부에 노출되면 안되는)

3.  이렇게 받은 client_id client_secret node.js 애플리케이션 내에 저장한다.

이렇게 해서 node.js 애플리케이션이 만들어졌으면 호출을 해보자. 호출은 다음과 같은 순서로 이루어 진다. 잠깐 용어를 짚고 넘어가면 OAuth 2.0에서는 Facebook 과 같이 Protected resource (API)를 제공하는 서버를 Resource Server 라고 하고, node.js와 같이 Protected resource (API)에 대한 접근을 요청하는 대상을 Client 라고 정의 한다. Client Resource Server에 접근할때, 사용자의 신분 (id,password)를 기반으로 하여, Resource Server에 접근하는데, 이러한 사용자를 User 라고 정의한다. 그러면 다음과 같은 흐름을 보자.



1.  먼저 사용자는 Web Browser에서, node.js 서버로 서비스를 요청한다.

2.  Node.js 서버는 사용자가 로그인이 되어 있지 않은 상태라면, Facebook log in URL에 대한 rediretion URL을 브라우져에게 보낸다. 이 때, node.js 서버임을 식별하기 위해서 client_id와 몇 가지 추가적인 정보를 보내는데, 그 내용은 다음과 같다.

Ÿ   client_id : 사용자를 통해서 인증을 요청하는 서비스가 앞에서 등록한 node.js 애플리케이션임을 알려준다.

Ÿ   redirect_url : 페이스북 인증서버에서 인증이 끝난후에, 인증 결과를 받을 node.js  애플리케이션으 HTTP URL을 정의한다.

Ÿ   scope : 페이스북에 접근을 요청한 리소스 목록을 정의한다. 예를 들어, 글쓰기, 읽기, 사진 올리기, 연락처 공유등과 같이 리소스에 대한 범주를 정의한다. Oauth 2.0의 특징중의 하나가 단순히 사용자 인증(Authentication)만을 하는 것이 아니라, 사용자가 소유한 리소스중, 접근을 허가 하는 권한 제어(Authorization) 기능을 함께 제공한다는 것이다.

https://oauth2 authorizationserver.com/auth?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos

실제 facebook redirection URL

https://www.facebook.com/login.php?skip_api_login=1&api_key=253044994897796&signed_next=1&next=https://www.facebook.com/v2.0/dialog/oauth?redirect_uri=http://localhost:8080/auth/facebook/callback&scope=read_stream&response_type=code&client_id=253044994897796&ret=login&cancel_uri=http://localhost:8080/auth/facebook/callback?error=access_denied&error_code=200&error_description=Permissions error&error_reason=user_denied#_=_&display=page

3.  Browser Redirect URL을 받아서,페이스북의 인증 서버 URL Redirect를 한다.

4.  페이스북 인증 서버는 log in page를 사용자에게 보낸다.



5.  사용자는 사용자 ID PASSWORD를 입력한다.

인증이 되고 나면, scope에 의해서 리소스에 대한 접근 요청을 허가할 것인지를 물어보는데, 아래는 node.js 애플리케이션이 글쓰기 권한을 요청했을때 사용자에게 node.js 애플리케이션에 글쓰기 권한을 허용할지를 물어보는 화면이다.



6.  페이스북의 인증서버는 ID,PASSWORD를 인증과 권한 획득에 성공하면, 인증과 권한 획득에 성공했다는  Authorization Code와 함께, 다시 2에서 정의된 Service Consumer node.js server callback URL (redirect_url= redirect_uri=http://localhost:8080/auth/facebook/callback ) redirect request를 보낸다.

7.  Browser는 위에서 받은 Authorization code와 함께, 앞에서 받은 node.js 서버의 callback URL redirect를 한다.

http://localhost:8080/auth/facebook/callback?code=AQAKlwhopD1DD5(중략)

8.  Node.js 서버는 이 사용자가 인증 되었음  증명하는 받은 authorization code를 가지고, 페이스북의 authorization server에 문의한다.
POST https://api.oauth2server.com/token
    grant_type=authorization_code&
    code=AUTH_CODE_HERE&
    redirect_uri=REDIRECT_URI&
    client_id=CLIENT_ID&
    client_secret=CLIENT_SECRET

Ÿ   grant_type : grant type은 앞에서 설명한 4개중에서, 현재 사용하는 grant type authorization code 방식이기 때문에 이를 정의한다.

Ÿ   code : 앞에서 받은 authorization code

Ÿ   redirect_uri

Ÿ   client_id : node.js 서버를 인증하기 위한 client id. 앞서 developer portal에서 발급받아서, node.js 서버 애플리케이션안에 넣어놓았다.

Ÿ   client_secret : node.js 서버를 인증하기 위한 client_secret(password). 앞서 developer portal에서 발급받아서, node.js 서버 애플리케이션안에 넣어놓았다.

9.  페이스북의 authorization server는 이 authorization code를 가지고해당 요청이 인증되었음을 확인하고, node.js 서버에게로 API 접근을 허용하는 access_token을 발급한다.

10. Node.js 서버는 이 access_token을 가지고 페이스북 API에 접근을 요청하면, 페이스북 API서버는 access_token을 통해서 API 접근 가능 여부를 판단한 후, 인가된 요청인 경우 API 요청을 수락하여 서비스를 제공한다.

간단하게 authorization code 방식을 이용한 애플리케이션 인증 방식에 대해서 알아보았다. 몇가지 더 짚고 넘어가면, authorization code 방식의 장점은 node.js 서버가 사용자 id,password를 알수 없다. API를 제공하는 서비스 제공자 입장에서, 3’rd party (협력 파트너)에게 자사의 사용자 id,password를 노출 시킬 필요가 없기 때문에, 파트너를 대상으로 안전하게 서비스 사용자의 id,password 없이 사용자 인증을 진행할 수 있다.

아울러 access_token에 대해서 몇가지 짚고 넘어갈것이 있는데, access_token을 취득한 이후에는 별도의 사용자 인증 절차가 필요하지 않다. Node.js 서버에서 access_token만을 기억하고 있으면 해당 사용자에 대한 리소스를 호출하는 API를 이 access_token을 통해서 호출할 수 있다. (즉 호출할때 마다 매번 사용자 인증을 할 필요가 없다는 말이다.)


다음글에서는 실제로 Passport 모듈을 이용하여 Facebook Open API를 호출하는 방법을 설명하도록 하겠다.

빠르게 훝어보는 node.js

#14 - Passport를 이용한 사용자 인증 구축

조대협 (http://bcho.tistory.com)


Passport node.js용 범용 인증 모듈이다. 기본적인 HTTP Basic Auth에서 부터 HTTP digest authentication, OAuth,OpenID 등 다양한 인증 프로토콜을 지원하며, Facebook이나 Twitter,Google등과의 연계된 SSO 인증을 포함하여 약 140가지의 인증 모듈을 포함한다. 그러면Passport를 이용하여, 어떻게 사용자 인증을 구현할 수 있는지 살펴보도록 하자.

이러한 다양한 인증에 대해서 미리 구현해 놓은 것을 passport에서는 Strategy라고 한다. Facebook 인증을 위해서는 facebook strategy Twitter 인증을 위해서는 Twitter Strategy를 사용한다.

Local Strategy

먼저 id,password를 받아서 자체 인증 처리를 하는 가장 간단한 LocalStrategy에 대해서 살펴보면서 개념을 이해해보도록 하자. Express 프로젝트를 생성한후

먼저 웹 인증을 하기 위해서는 HTML Form으로 부터 사용자 id passwd를 받기 위한 페이지를 생성하자.

HTML파일은 express에서 static 페이지를 저장하는 /public 디렉토리 아래에 /public/login.html로 아래와 같이 정의 하였다.


<!DOCTYPE html>

<html>

<head>

    <title></title>

</head>

<body>

<form action="/login" method="post">

    <div>

        <label>UserId:</label>

        <input type="text" name="userid"/><br/>

    </div>

    <div>

        <label>Password:</label>

        <input type="password" name="password"/>

    </div>

    <div>

        <input type="submit" value="Submit"/>

    </div>

</form>

</body>

</html>

이 폼에서, 사용자 id“userid”라는 필드로, 사용자 비밀번호는 “password”라는 이름의 필드로 입력 받도록 하였다.



Passport LocalStrategy 는 디폴트로, username이라는 필드와 password라는 필드로 각각 로그인 id와 비밀번호를 받도록 되어 있는데, 이 예제에서는 username 대신 앞의 폼에서 정의한 userid라는 필드로 사용자 id를 입력 받도록 변경할것이다.

app.js에서 Passport local strategy를 사용하기 위해서 모듈을 불러 들이자.

var passport = require('passport')

    , LocalStrategy = require('passport-local').Strategy;

다음으로, passport LocalStrategy를 정의하고, Local Strategy에 의해서 인증시에 호출되는 인증 메서드를 정의한다..

passport.use(new LocalStrategy({

        usernameField : 'userid',

        passwordField : 'password',

        passReqToCallback : true

    }

    ,function(req,userid, password, done) {

        if(userid=='hello' && password=='world'){

            var user = { 'userid':'hello',

                          'email':'hello@world.com'};

            return done(null,user);

        }else{

            return done(null,false);

        }

    }

));

LocalStrategy 객체를 생성한 후에, 첫번째로 세팅값을 설정한다.

usernameField passwordField라는 인자는,LocalStrategy HTML 폼으로 어떤 필드를 각각 사용자 id  pasword 읽어드릴지를 정의하는 옵션이다. 별도로 정의하지 않았을 경우 “username” “password”라는 필드를 사용하지만, 여기서는 “userid” 라는 필드로 부터 값을 읽어드리도록 변경하였다.

passReqToCallback이라는 옵션은 인증을 수행하는 인증 함수로 HTTP request 그대로  전달할지 여부를 결정한다.

다음으로는 인증시에 호출되는 인증 함수를 정의한다.인증 함수로는 4개의 인자가 전달된다. HTTP request 전달하는 req, 사용자 id,passwd 전달하는 인자가 각각 들어가고 마지막으로 callback 함수인 done 전달한다.

함수에서, 실제로 사용자 인증을 수행하게 된다.

예제는 아주 간단한 예제이기 때문에, 사용자 id “hello”이고, 비밀번호가 “world” 경우 인증이 성공한것으로 처리하도록 하였다. 실제 프로그램에서는 사용자 데이타베이스에서 사용자 id 비밀번호를 조회해서 비교하도록 하면 된다.

인증이 성공하였을 경우 done callback함수에, null 함께, user라는 인자를 넘기게 된다.

user라는 인자는 객체로 사용자에 대한 로그인 정보를 저장하는 객체로 사용자 이름이나, ID등의 정보를 저장할 있다. 로그인 후에는 HTTP Session 저장된다. 예제에서는 간단하게, 사용자 id 사용자 email 저장하도로 하였다.

만약에 인증이 실패하였을 경우에는 done callback함수에 done(null,false) 리턴하게 되면 인증 실패 메세지를 내게 된다.


Serialize Deserialize

로그인이 성공하면, serializeUser 메서드를 이용하여 사용자 정보를 Session 저장할 있다.

passport.serializeUser(function(user, done) {

    console.log('serialize');

    done(null, user);

});

serializeUser 메서드에서는 function(user,done) 이용해서 session 저장할 정보를 done(null,user) 같이 두번째 인자로 넘기면 된다. 이때 user 넘어오는 정보는 앞의 LocalStrategy 객체의 인증함수에서 done(null,user) 의해 리턴된 값이 넘어온다.

예제에서는 user 객체 전체를 사용자 session 저장하였다.

다음으로, node.js 모든 페이지에 접근할때, 로그인이 되어 있을 경우 모든 사용자 페이지를 접근할 경우 deserilizeUser 발생한다. deserializeUser에서는 session 저장된 값을 이용해서, 사용자 Profile 찾은 , HTTP Request  리턴한다.

// 인증 , 페이지 접근시 마다 사용자 정보를 Session에서 읽어옴.

passport.deserializeUser(function(user, done) {

    //findById(id, function (err, user) {

    console.log('deserialize');   

    done(null, user);

    //});

});

deserializeUser callback함수의 첫번째 인자로 넘어오는 내용”user” 세션에 저장된 사용자 정보이다. 예제에서는 session 해당 사용자의 정보를 저장하였기 때문에 별도의 변경없이 done(null,user) 이용해서 그대로 session에서 읽은 내용을 리턴한다.

이렇겍 리턴된 내용은 HTTP Request “req.user” 값으로 다른 페이지에 전달된다.

설명이 복잡한데, 배경을 설명하면 다음과 같다. Session 사용자 정보를 저장하고자할 경우, 사용자 정보가 크다면, 메모리가 많이 소모되기 때문에, serializeUser시에, 사용자 id 같은 정보만 저장하도록 하고, 페이지가 접근될때 마다 deserilizeUser 수행되면,세션에 저장된 사용자 id 이용하여 데이타베이스에서 사용자 정보를 추가로 select해서 HTTP request 붙여서 리턴하는 형태를 사용한다.

실제로도 PassPort 공식 메뉴얼을 보면 다음과 같이 가이드를 하고 있다.

passport.serializeUser(function(user, done) {

          done(null, user.id);

        });

 

passport.deserializeUser(function(id, done) {

          User.findById(id, function(err, user) {

            done(err, user);

          });

        });

serialize시에 session에서는 user.id 사용자 id값만 저장하고, deserialize시에는 session 저장된 id 이용해서, DB 매번 사용자 정보를 select하는 모습을 있다.

그러나 이렇게 하게 되면, 실제 Session 저장되는 데이타가 적어서 메모리는 어느 정도 절약할 있지만 페이지 접근시마다 매번 DB select 발생하기 때문에 성능에 많은 저하가 오기 때문에 권장하고 싶지 않다. 아주 데이타를 넣지 않는 이상 10~20개정도의 필드는 HTTP Session 저장해도 문제가 없기 때문에, 앞에서 필자가 소개한 방식대로 사용자 로그인 데이타를 모두 serialize시에 session 넣는 것을 권장한다. 만약에 데이타가 너무 많아서 메모리 사용량이 우려될 경우에는 redis 같은 외부 메모리 DB 이용해서 session 정보를 저장하도록 하는 방법을 사용하자.

다음으로, Express 객체를 생성한 후에, express에서 passport initialize하고, passport에서 session 사용하도록 설정한다.

app.use(passport.initialize());

app.use(passport.session());


Route 모듈과 연동

이제 LocalStrategy에 대한 인증 모듈과 인증후 사용자 정보를 세션에 넣고 빼는 serilizeUser deserializeUser 메서드를 구현했으니 이제, login 창에서 들어오는 userid passwd를 가지고 인증 모듈을 실행하도록 연결을 해보자.

Passport connect 미들웨어의 형태로 동작한다. 특정 URL로 들어오는 log in 요청에 대해서 connect 미들웨어 형태로, passport 인증 모듈을 수행하도록 할 수 있다, Express route 미들웨어 등과 통합이 가능하다. passport.authenticate라는 메서드를 이용하는데,

app.get(/URL,passport.authenticate(‘{strategy}’),function(req,res){

           :

        }

이 코드는 ‘/URL’로 들어오는 요청에 대해서 사용자 인증을 처리하도록 passport.authentication 을 중간에 미들웨어 형태로 끼워 놓은 것이다. 인증에 성공하게 되면, 뒤에 정의된 callback 함수 인 function(req,res)로 요청이 연결되고, 만약에 인증이 실패하면 401 Unauthorized HTTP response를 리턴한다. Authenticate의 첫번째 인자로 정의된 ‘{strategy}’passport의 인증 Strategy를 정의한다.

위의 코드에서 처럼 passport 미들웨어에서 인증이 완료된 call back function을 실행하는 방법 말고, 인증 성공/실패에 따라서 다른 페이지로 redirect할 수 있도록 설정할 수 있다.

app.post(‘/URL,passport.authenticate(‘{strategy}’,{successRedirect:’/success’

        failureRedirect:’/failure’});

 

위의 코드는 인증이 성공했을때에는 /success URL로 리다이렉트하고, 실패했을 경우에는 /failure라는 URL redirect하도록 한다

그러면 두번째 소개한 redirect 방식으로, passport.authenticate 메서드를 연결해보자

app.post('/login',

    passport.authenticate('local', { failureRedirect: '/login_fail', failureFlash: true }),

    function(req, res) {

        res.redirect('/login_success');

    });

login.html에서 POST action으로 들어온 인증처리를 /login에서 하도록 하고 passport.authenticate ‘local’ strategy 호출한다.

그리고 로그인이 성공했을 경우에는 ‘/login_sucess’ redirect하고, 실패했을 경우에는 ‘/login_fail’ redirect하도록 하였다.

페이지별로 로그인이 되었는지를 확인하고, 로그인이 되어 있을 경우 HTTP request에서 사용자 정보를 가지고 오는 코드는 다음과 같다.

app.get('/login_success', ensureAuthenticated, function(req, res){

    res.send(req.user);

   // res.render('users', { user: req.user });

});

request.user 이용하면, deserializeUser 의해서 저장된 사용자 정보를 꺼내볼 있다

Connect 미들웨어의 특성을 이용하여, 호출시마다 ensureAuthenticated라는 메서드를 호출하게 해서, 로그인이 되어 있는지를 확인하도록 있는데,

function ensureAuthenticated(req, res, next) {

    // 로그인이 되어 있으면, 다음 파이프라인으로 진행

    if (req.isAuthenticated()) { return next(); }

    // 로그인이 안되어 있으면, login 페이지로 진행

    res.redirect('/login.html');

}

같이 로그인이 되어 있으면, req.isAuthenticated() true 리턴된다.

 

지금까지 설명한 내용을 정리해보자



passport에서 인증은 express router에서 passort.authenticate 메서드를 정의함으로써 인증 메서드를 호출할 있다. 이때 passport에서 사용한 인증 Strategy 정의한다.

authenticate에서 정의한 인증 Strategy 대한 개게를 passport.use 이용해서 정의하고, 안에, 인증 함수를 구현한다. 인증함수에서는 HTML form 필드를 통해서 받은 id password 전달되는데, DB등을 연동하여, 들어온 id passwd 비교한후에, 성공하였을 경우 done(null,user) 정보로 리턴을 한다.

이렇게 인증 성공으로 리턴이 되면 사용자에 대한 정보를 세션에 저장할 있도록 passport.serailizeUser 앞에서 done(null,user) 통해서 전달한 user 정보가 전달된다.

그러면 passport.serializeUser에서는 전달된 user 객체중에서 세션에 저장할 정보만을 done(null,user) HTTP session 저장한다.



일단 인증이 되어 로그인이된 사용자는 request마다 passport.desrializeUser 메서드를 호출하게 되는데, 앞에서 passport.serializeUser에서 저장한 사용자 id 키를 이용해서, 사용자 정보를 DB등에서 조회하거나 하여, done(null,user) 리턴하면, HTTP request 함께 리턴이 된다.

해당 사용자가 로그인이되어 있는지를 확인하려면 req.isAuthenticated 이용하여 로그인 되었는지 여부를 확인할 있고, 페이지별로 로그인이 되어 있는지에 대해서 접근제어를 하려면 함수를 하나 정의한 후에, Express router connect 미들웨어로 전처리로 호출하게 해서 인증 여부를 확인하도록 하면 된다.

 

지금까지 간단하게 LocalStrategy 통해서 Passport 대해서 알아보았다.다음에는 Passport-facebook Strategy 통해서 OAuth2 facebook 인증에 대해서 알아보도록 하겠다.


sample code : https://github.com/bwcho75/node.js_study/tree/master/Passport-Localstrategy