생활 코딩 강의 목록 : https://opentutorials.org/module/2026/12063
인프런 강의 목록 : https://www.inflearn.com/course/node-js-활용/unit/3586?tab=curriculum
1. 강좌소개
서버측 자바스크립트 기술, Node.js 를 소개하고 사용법을 학습해 간단한 웹 어플리케이션을 만들어 보는 nodejs 강좌입니다. 기본 javascript 와 nodejs 를 이용하기 때문에 타 언어 (PHP 나 JSP) 의 도움없이 오직 javascript 기술로만 웹 어플리케이션을 구현할 수 있는 지식을 전달해줍니다. 앞선 두 강좌에서 학습한 내용과 실습예제들을 통합해 게시물과 로그인 기능이 있는 웹어플리케이션을 직접 만들어 볼수 있는 강좌입니다.
- 선수강좌가 2개 있습니다. 아래것을 먼저 학습하신후 본 강좌를 들으시면 좀더 이해가 쉬우실거에요.
- 자바스크립트의 기초적인 문법은 설명하지 않습니다. 여러 자바스크립트에 대한 다른 수업은 >>여기<< 에서 학습 가능하세요.
- 강의에서 사용하는 소스코드 링크 링크바로가기
프로젝트 준비
1) Take-Nodejs 디렉토리 생성
2) $ cd Take-Nodejs
3) $ express .
4) 넌적스 설치 및 vscode 툴에 Nunjucks Snippets 확장패키지 설치
$ npm i nunjucks --save
5)nodemon 설치 및 package.json 변경
"scripts": { "start": "node ./bin/www", "dev": "nodemon ./bin/www", "debug": "DEBUG=myapp:* yarn start" },
6)app.js 변경
var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); const nunjucks = require('nunjucks'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); //app.set('view engine', 'jade'); app.set('view engine', 'html'); // 확장자를 html 로도 사용이 가능함. nunjucks.configure('views', { // views폴더가 넌적스파일의 위치가 됨 express: app, watch: true, }); ~
7)넌적스 레이아웃 만들기
view/layout.html
<!DOCTYPE html> <html lang="ko"> <head> <title>{{title}}</title> <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> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body> <header> {% block header %} {% endblock %} </header> {% block content %} {% endblock %} <footer> {% block footer %} {% endblock %} </footer> </body> </html>
routes/index.js
var express = require('express'); var router = express.Router(); /* GET home page. */ // router.get('/', function (req, res, next) { // res.render('index', { title: 'Express' }); // }); router.get('/', function (req, res, next) { res.render('index', { title: 'Nunjucks' }); // index.html에 title이라는 변수를 전달 }); module.exports = router;
views/index.html
{% extends 'layout.html' %} {% block content %} <h1>{{title}}</h1> <p>Welcome to {{title}}</p> {% endblock %}
8)실행
$ npm run dev
https://mozilla.github.io/nunjucks/
https://mozilla.github.io/nunjucks/templating.html#sort-arr-reverse-casesens-attr
소스 : https://github.com/braverokmc79/Take-Nodejs
아래 링트 참조 :
Node.Js - Express (생활코딩) - 7 .Facebook Login , Passport Mysql 을 이용한 OAuth2.0 회원 가입 및 로그인 연동
타사인증 (Federation authentication)
27.Passportjs 1 : intro
28.PFederation authentication 2 : facebook
페이스북 로그인 설정하기
방문자가 Facebook 계정으로 내 사이트에 가입하고 로그인할 수 있도록 Facebook 로그인을 설치할 수 있습니다. 이 도움말에서는 Facebook 로그인을 설치하는 방법을 단계별로 안내합니다.
주의: 2019년 3월부터 Facebook 정책 변경에 따라 Facebook 로그인을 사용하기 위해선 SSL 보안서버 인증서를 설치해야 합니다. 자세한 설치방법은 SSL 보안서버 인증서 설치하기 도움말에서 확인할 수 있습니다.
https://imweb.me/faq?mode=view&category=29&category2=47&idx=71636
29.PFederation authentication 3
30.Federation authentication 4
31.Federation authentication 5
[Express + Nunjucks (넌적스 ) + Mysql + Passport 페이스북 로그인]
참조 : https://macaronics.net/index.php/m03/nodejs/view/1948
소스 : https://github.com/braverokmc79/Take-Nodejs
vscode 툴로 개발 소스 보기 : https://github.dev/braverokmc79/Take-Nodejs
1. 페이스북 설정 및 https 설정
다음을 참조
https://imweb.me/faq?mode=view&category=29&category2=47&idx=71636
페이스북에 유효한 OAuth 리디렉션 URI 설정
이 프로젝트 에서는 https://localhost:3000/passport/oauth2/redirect/facebook 를 리다엑트로 설정 하였다.
Facebook 로그인 => 설정
페이스북에서 제시하는 "/oauth2/redirect/facebook " 경로가 아닌 localhost 는 https 를 설정해야 한다.
mkcert 사용 및 윈도우 설치
참조 : https://macaronics.net/index.php/m03/nodejs/view/1948
프로젝트 구성
bin/www
#!/usr/bin/env node /** * Module dependencies. */ var app = require('../app'); var debug = require('debug')('take-nodejs:server'); var http = require('http'); const https = require('https'); const fs = require('fs'); /** * Get port from environment and store in Express. */ var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ //var server = http.createServer(app); /** * Listen on provided port, on all network interfaces. */ var server = https.createServer( { // key: fs.readFileSync(__dirname + '/key.pem', 'utf-8'), // cert: fs.readFileSync(__dirname + '/cert.pem', 'utf-8'), key: fs.readFileSync('C:/https/localhost-key.pem', 'utf-8'), cert: fs.readFileSync('C:/https/localhost.pem', 'utf-8'), }, app.use('/', (req, res) => { //res.send('Congrats! You made https server now :)'); }) ).listen(port); //server.listen(port); ~
2. 라이브러리 설치 및 env 설정
$ npm i passport $ npm i passport-facebook $ npm i dotenv
* dotenv 는 env 는 환경 변수를 사용을 위해 설치한다.
app.js
const dotenv = require("dotenv") dotenv.config();
.env
FACEBOOK_CLIENT_ID=__INSERT_CLIENT_ID_HERE__ FACEBOOK_CLIENT_SECRET=__INSERT_CLIENT_SECRET_HERE__
3. Mysql 테이블 생성 및 연동
1) mysql 설치
$npm i mysql
2) express-mysql-session 설치
$ npm i express-mysql-session
3)테이블 생성 및 권한 부여
CREATE database opentutorials; CREATE USER `opentutorials`@`localhost` identified by '1111'; GRANT ALL privileges on opentutorials.* to `opentutorials`@`localhost` ;
schema.sql
CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(30) unique NOT NULL, `email` varchar(30) unique NOT NULL, `hashed_password` varchar(100) DEFAULT NULL, `salt` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; CREATE TABLE `federated_credentials` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) unique NOT NULL, `provider` varchar(100) unique NOT NULL, `subject` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`) , FOREIGN KEY (user_id) REFERENCES users(id) )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Usage
샘플 사용예 )
var express = require('express'); var app = module.exports = express(); var session = require('express-session'); var MySQLStore = require('express-mysql-session')(session); var options = { host: 'localhost', port: 3306, user: 'session_test', password: 'password', database: 'session_test' }; var sessionStore = new MySQLStore(options); app.use(session({ key: 'session_cookie_name', secret: 'session_cookie_secret', store: sessionStore, resave: false, saveUninitialized: false }));
lib/db.js
const mysql = require('mysql'); const db = mysql.createConnection({ host: 'localhost', user: 'opentutorials', password: '1111', database: 'opentutorials' }); db.connect(); module.exports = db;
4. app 설정 ( session MySQLStore 설정 및 env , passport require시에 파라미터값 app 값과 passport 설정 주의 )
app.js
passport require시에 파라미터값 app 값과 passport 설정 주의
const passport = require("./lib/passport")(app);
const passportRouter = require('./routes/passport/index')(passport);
const createError = require('http-errors'); const express = require('express'); const path = require('path'); const cookieParser = require('cookie-parser'); const logger = require('morgan'); const nunjucks = require('nunjucks'); const flash = require('connect-flash'); const dotenv = require("dotenv"); const app = express(); const session = require('express-session'); const MySQLStore = require('express-mysql-session')(session); const MongoDBStore = require('connect-mongodb-session')(session); const FileStore = require('session-file-store')(session); dotenv.config(); //2. MySQLStore 설정 // session DB 저장 방식 - session 테이블이 자동 생성되고 세션이 passport의해 저장 된다. app.use(session({ secret: '12312dajfj23rj2po4$#%@#', resave: false, saveUninitialized: true, store: new MySQLStore({ host: 'localhost', port: 3306, user: 'opentutorials', password: '1111', database: 'opentutorials' }) })); app.use(flash()); const passport = require("./lib/passport")(app); // view engine setup app.set('views', path.join(__dirname, 'views')); //app.set('view engine', 'jade'); app.set('view engine', 'html'); // 확장자를 html 로도 사용이 가능함. nunjucks.configure('views', { // views폴더가 넌적스파일의 위치가 됨 express: app, watch: true, }); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser('3213e1da4523fa')); app.use(express.static(path.join(__dirname, 'public'))); const indexRouter = require('./routes/index'); const usersRouter = require('./routes/users'); const cookieRouter = require('./routes/cookies/index'); const sessionRouter = require('./routes/session/index'); const passportRouter = require('./routes/passport/index')(passport); app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/cookies', cookieRouter); app.use('/session', sessionRouter); app.use('/passport', passportRouter); ~
5.URL 설정
routes/passport/index.js
const express = require('express'); const router = express.Router(); const db = require("../../lib/db"); global.crypto = require('crypto') module.exports = function (passport) { ~ //1.페이스북그인 처리 - 로그인 버튼 클릭시 router.get('/login/federated/facebook', passport.authenticate('facebook', { scope: 'email' })); //2.페이스북그인 처리 - 콜백 반환 router.get('/oauth2/redirect/facebook', passport.authenticate('facebook', { successRedirect: '/passport/welcome', failureRedirect: '/passport/login', failureFlash: true, successFlash: true })); ~ return router; }
6. passport 페이스북 로그인 처리
★현재 프로젝트에서는 적용하지 않았으나,
passReqToCallback: true 옵션값을 넣어 req 파라미터값을 사용할 수 있다.
passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password', passReqToCallback: true }, function verify(req, email, password, done) { console.log('local-join callback called'); console.log(email, password, done); } ));
lib/passport.js
const passport = require("passport"); const crypto = require('crypto'); const db = require("../lib/db"); const LocalStrategy = require('passport-local'); const FacebookStrategy = require('passport-facebook'); module.exports = function (app) { app.use(passport.initialize()); app.use(passport.session()); passport.serializeUser(function (user, cb) { console.log("로그인 처리시 최초 한번 passport.serializeUser 호출 user 값 :", user); process.nextTick(function () { //다음 내용을 세션에 저장 cb(null, { id: user.id, eamil: user.email, username: user.username }); }); }); // 로그인 성공되면 passport.deserializeUser 매번 실행 처리된다 passport.deserializeUser(function (user, cb) { console.log("deserializeUser :", user); process.nextTick(function () { return cb(null, user); }); }); // 로그인 처리 - 2) passport 로그인 passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' }, function verify(email, password, cb) { db.query('SELECT * FROM users WHERE email = ?', [email], function (err, row) { if (err) { return cb(err); } if (!row[0]) { return cb(null, false, { message: '등록된 이메일이 없습니다.' }); } if (!row[0].salt) return cb(null, false, { message: '소셜 로그인으로 등록된 유저입니다.' }) crypto.pbkdf2(password, row[0].salt, 310000, 32, 'sha256', function (err, hashedPassword) { console.log("로그인 데이터 : ", password, row[0].salt, hashedPassword.toString('hex')); console.log("로그인 DB암호: ", row[0].hashed_password); if (err) { return cb(err); } if (row[0].hashed_password !== hashedPassword.toString('hex')) { console.log("비밀번호 오류"); //flash 에 저장 처리됨 - "flash":{"error":["비밀번호가 일치하지 않습니다."]}} return cb(null, false, { message: '비밀번호가 일치하지 않습니다.' }); } console.log("로그인 성공"); return cb(null, row[0], { message: '로그인 성공' }); }); }); })); //페이스북 로그인 passport.use(new FacebookStrategy({ clientID: process.env['FACEBOOK_CLIENT_ID'], clientSecret: process.env['FACEBOOK_CLIENT_SECRET'], callbackURL: '/passport/oauth2/redirect/facebook', state: true, profileFields: ['id', 'emails', 'name', 'displayName'], }, function verify(accessToken, refreshToken, profile, cb) { console.log(" 페이스북 로그인 ", profile); db.query('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [ 'Facebook', profile.id ], function (err, row) { if (err) { return cb(err); } console.log(" row.length ", row.length, profile.emails[0].value); //1.federated_credentials 테이블에 등록되어 있지 않으면 신규등록처리 if (row.length === 0) { db.query('SELECT * FROM users WHERE email = ?', [profile.emails[0].value], function (err, results) { console.log("등록된 이메일이 존재 results : ", results); if (results.length > 0) { //1) User 테이블에 등록되어 있다면 federated_credentials 테이블에만 등록후 로그인 처리 db.query('INSERT INTO federated_credentials (user_id, provider, subject) VALUES (?, ?, ?)', [ results[0].id, 'Facebook', profile.id], function (err) { //구글 페이스북 등 여러 소셜에 대한 //user_id 중복 오류가 나올수 있다. 따라서, 에러 상관 없이 로그인 처리 return cb(null, results[0]); }); } else { //1) User 테이블 및 federated_credentials 테이블 모두 등록 되어 있지 않다면 모두 등록후 로그인 처리 db.query('INSERT INTO users (username, email) VALUES (?, ?)', [ profile.displayName, profile.emails[0].value ], function (err, results, fields) { if (err) return cb(err); const id = results.insertId; db.query('INSERT INTO federated_credentials (user_id, provider, subject) VALUES (?, ?, ?)', [ id, 'Facebook', profile.id ], function (err) { if (err) { return cb(err); } const user = { id: id, username: profile.displayName }; return cb(null, user); }); }); } }); } else { //2.federated_credentials 테이블 등록되어 있다면 바로 로그인처리 db.query('SELECT * FROM users WHERE id = ?', [row[0].user_id], function (err, row) { if (err) { return cb(err); } if (!row[0]) { return cb(null, false); } return cb(null, row[0]); }); } }); })); return passport; }
7. 화면 뷰
views/passport/login.html
{% extends '../layout.html' %} {% block content %} <h1>Login</h1> <p><a href="/passport/signup">회원가입</a></p> <span style="color:red">{{error}}</span> <span style="color:rgb(0, 119, 255)">{{success}}</span> <form action="/passport/login_process" method="post"> <p><input type="email" name="email" placeholder="email"></p> <p><input type="password" name="password" placeholder="password"></p> <p> <input type="submit" value="login"> <a class="btn facebook" href="/passport/login/federated/facebook"><span class="icon">페이스북으로 로그인</span></a> </p> </form> {% endblock %}
실행
$ npm run dev
https 로 테스트 할것
테스트 주소
https://localhost:3000/passport/login
댓글 ( 4)
댓글 남기기