본문 바로가기

웹_프론트_백엔드/프론트엔드

2021.03.13

1. passport 모듈
 : 노드에서 사용할 수 있는 사용자 인증 모듈이다.
   사용 방법이 간단할 뿐만 아니라, 사용자 인증 기능을 독립된 모듈 안에서 진행할 수 있도록 도와준다.
   특히 익스프레스를 사용할 경우 미들웨어로 사용할 수 있어서 간단한 설정만으로 로그인 기능을 만들 수 있다.

 

 

 

2. 페이스북 개발자 센터

  https://developers.facebook.com

 

 

3. 페이스북 개발자 센터에 앱 만들기

 : 로그인 후 내 앱 클릭 > 앱 만들기 클릭

> 연결된 환경 구축 선택 후 계속 클릭(페이스북 로그인 사용만 해볼 예정) > 표시할 앱 이름 설정 후 앱 만들기 클릭

> 앱 만들기 완...!

 

 

4. 페이스북 로그인을 사용하기 위한 설정

 : Facebook 로그인 설정 클릭 > www 웹 클릭

> 사이트 URL이 존재하지 않으므로 http://localhost:3000으로 설정 후 save 클릭

> FaceBook 로그인의 설정 클릭 > 클라이언트 OAuth 로그인, 웹 OAuth 로그인의 설정을 로 변경

 

 

5. 코드와 연결해줄때, 앱 ID와 앱 시크릿 코드를 이용해야 한다.

 

 

6. 대부분 홈페이지를 만들 때, 사이트 서버와 DB 서버를 분리시키는 경우가 많다.
   왜냐하면 사이트에 부하가 가면 DB도 느려질 수 있기 때문이다.

 

 

7. [참고] 자바스크립트에서 자바 스크립트를 부를때는 확장자를 쓰지 않는다.

 

 

8. [마무리 못한 부분은 다음시간에 계속할 예정] passport 모듈을 이용한 로컬 회원가입, 로그인

** 1_possport.js

const express = require('express');
const bodyParser = require('body-parser');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');

const passport = require('passport');                           // npm i passport

const static = require('serve-static');
const path = require('path');
const expressErrorHandler = require('express-error-handler');   // npm i express-error-handler
const app = express();
const router = express.Router();

app.use(cookieParser());
app.use(expressSession({
    secret: '!@#$%^&^*&(',
    resave: false,
    saveUninitialized: true,
    cookie: { maxAge: 60 * 60 * 1000 }
}));
app.use(logger('dev'));
// passport의 세션을 사용하려면 그 전에 express의 세션을 사용하는 코드가 먼저 나와야 한다.
app.use(passport.initialize());        // passport 초기화
app.use(passport.session());
app.use(bodyParser.urlencoded({extended: false}));
app.use('/public', static(path.join(__dirname, "public")));

app.use('/', router);
const errerHandler = expressErrorHandler({
    static: {
        '404': './public/404.html'
    }
});

app.use(expressErrorHandler.httpError(404));
app.use(errerHandler);

app.set('views', __dirname + "/views");
app.set('view engine', 'ejs');

const config = require('./config/config');
const database = require('./database/database');

const configPassport = require('./config/passport');
configPassport(app, passport);

app.listen(config.server_port, () => {
    console.log(`${config.server_port} 포트로 서버 실행 중...`);
    database.init(app, config);
});

 

** /public/404.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>페이지를 찾을 수 없습니다.</title>
    <style>
        body { background-color: deepskyblue; }
        h2 { font-size: 80px; }
        p { font-size: 20px; }
        h2, p { text-align: center; }
    </style>
</head>
<body>
    <h2>404</h2>
    <p>요청한 페이지를 찾을 수 없습니다.</p>
</body>
</html>

 

** /config/config.js

module.exports = {
    server_port: 3000,
    db_url: 'mongodb://localhost:27017/frontenddb',
    db_schemas: [{file:'./member_schema', collection:'member2', schemaName:'MemberSchema', modelName:'MemberModel'}],
    facebook: {
        clientID: '앱 ID',
        clientSecret: '앱 시크릿 코드',
        callbackURL: 'http://localhost:3000/auth/facebook/callback'
    }
}

 

** /database/database.js

const mongoose = require('mongoose');

let database = {};

database.init = function(app, config) {
    console.log('database init() 호출!');
    connect(app, config);
}

function connect(app, config) {
    console.log('connect() 호출!');
    mongoose.Promise = global.Promise;
    mongoose.connect(config.db_url);
    database.db = mongoose.connection;

    database.db.on('error', console.error.bind(console, 'mongoose connection error'));
    database.db.on('open', () => {
        console.log('데이터베이스 연결 성공');
        createSchema(app, config);
    });
}

function createSchema(app, config) {
    const schemaLen = config.db_schemas.length;
    console.log(`스키마의 개수 : ${schemaLen}`);

    for(let i=0; i<schemaLen; i++) {
        /*
            {
                file: './member_schema',   
                collection: 'member2',     
                schemaName: 'MemberSchema',
                modelName: 'MemberModel'   
            }
        */
        let curItem = config.db_schemas[i];
        let curSchema = require(curItem.file).createSchema(mongoose);
        console.log(`${curItem.file} 모듈을 불러온 후 스키마를 정의함`);

        let curModel = mongoose.model(curItem.collection, curSchema);
        console.log(`${curSchema.collection} 컬렉션을 위해 모델을 정의함`);

        database[curItem.schemaName] = curSchema;   // database[MemberSchema]
        database[curItem.modelName] = curModel;     // database[MemberModel]
        console.log(`스키마이름[${curItem.schemaName}], 모델이름[${curItem.modelName}]이 데이터베이스 객체의 속성으로 추가되었습니다.`)
        app.set('database', database);
        console.log('database 객체가 app 객체의 속성으로 추가됨');
    }
}

module.exports = database;

 

** /database/member_schema.js

const { Schema } = require('mongoose');
const crypto = require('crypto');       // npm i crypto

Schema.createSchema = function(mongoose) {
    console.log('createSchema() 호출!');
    const MemberSchema = mongoose.Schema({
        userid: {type:String, require:true, default: ''},
        hashed_password: {type:String, default:''},
        name: {type:String, default:''},
        salt: {type:String},
        age: {type:Number, default:0},
        create_at: {type:Date, default:Date.now},
        update_at: {type:Date, default:Date.now},
        provider: {type:String, default:''},
        authToken: {type:String, default:''},
        facebook: {}
    });

    MemberSchema.virtual('userpw')
                .set(function(userpw) {
                    this._userpw = userpw;
                    this.salt = this.makeSalt();
                    this.hashed_password = this.encryptPassword(userpw);
                })
                .get(function() {
                    return this._userpw;
                });

    MemberSchema.method('makeSalt', function() {
        console.log('makeSalt() 호출!');
        return Math.round((new Date().valueOf() * Math.random())) + '';   // 문자열
    });

    MemberSchema.method('encryptPassword', function(plainText, inSalt) {
        if(inSalt) {    // 로그인
            return crypto.createHmac('sha1', inSalt).update(plainText).digest('hex');
        } else {        // 회원가입
            // salt에 저장된 값을 가져와서 sha1 암호화를 통해 plainText를 섞어줌 -> 16진수로 변환
            return crypto.createHmac('sha1', this.salt).update(plainText).digest('hex');
        }
    });

    MemberSchema.method('authenticate', function(plainText, inSalt, hashed_password) {
        if(inSalt) {
            console.log('authenticate() 호출! <inSalt 있음>');
            return this.encryptPassword(plainText, inSalt) == hashed_password;
        } else {
            console.log('authenticate() 호출! <inSalt 없음>');
            return this.encryptPassword(plainText) == this.hashed_password;
        }
    });

    // pre() : 특정 작업이 일어나기 전에 미리 호출되는 메소드이다.
    //         트리거 역할
    MemberSchema.pre('save', (next) => {
        if(!this.isNew) return next();

        if(!validatePresenceOf(this.userid)) {
            next(new Error('유효하지 않은 password입니다.'));
        } else {
            next();
        }
    });

    const validatePresenceOf = function(value) {
        return value && value.length;       // 데이터가 있는지 여부
    }

    console.log('MemberSchema 정의 완료!');
    return MemberSchema;
}

module.exports = Schema;

 

** /config/passport.js

const local_signup = require('./passport/local_signup');
const local_login = require('./passport/local_login');

module.exports = function(app, passport) {
    console.log('config/passport 호출!');
}

 

** /config/passport/local_signup.js

// passport 회원가입
// 1. 로컬 회원가입
// npm i passport-local
const LocalStrategy = require('passport-local').Strategy;

module.exports = new LocalStrategy({
    usernameField: 'userid',
    passwordField: 'userpw',
    passReqToCallback: true
}, (req, userid, userpw, done) => {
    const name = req.body.name;
    const age = req.body.age;
    console.log(`passport의 local-signup 호출 : userid=${userid}, userpw=${userpw}, name=${name}, age=${age}`);

    // 실행문장에서 데이터가 blocking 되지 않도록 사용 => async 방식으로 변경
    process.nextTick(() => {
        let database = req.app.get('database');
        database.MemberModel.findOne({userid:userid}, (err, user) => {
            if(err) return done(err);

            if(user) {
                console.log('이미 가입된 계정입니다.')
                return done(null, false);
            } else {
                let user = new database.MemberModel({userid:userid, userpw:userpw, name:name, age:age});
                user.save((err) => {
                    if(err) throw err;

                    console.log('가입완료');
                    return done(null, user);
                });
            }
        });
    });
});

 

** /config/passport/local_login.js

// passport 로그인
// 1. 로컬 로그인
const LocalStrategy = require('passport-local').Strategy;

module.exports = new LocalStrategy({
    usernameField: 'userid',
    passwordField: 'userpw',
    passReqToCallback: true
}, (req, userid, userpw, done) => {
    console.log(`passport의 local-login 호출 : userid=${userid}, userpw=${userpw}`);
    let database = req.app.get('database');
    database.MemberModel.findOne({userid:userid}, (err, user) => {
        if(err) return done(err);

        if(!user) {
            console.log('계정이 존재하지 않습니다.');
            return done(null, false);
        }
        let authenticate = user.authenticate(userpw, user.salt, user.hashed_password);
        if(!authenticate) {
            console.log('비밀번호가 일치하지 않습니다.')
            return done(null, false);
        }
        return done(null, user);
    });
});

 

'웹_프론트_백엔드 > 프론트엔드' 카테고리의 다른 글

2021.03.20  (0) 2021.04.20
2021.03.14  (0) 2021.04.18
2021.03.07  (0) 2021.04.02
2021.03.06  (0) 2021.03.14
2021.02.28  (0) 2021.03.01