생활 코딩 강의 목록 : https://opentutorials.org/module/3590
인프런 강의 목록 : https://www.inflearn.com/course/node-js-express/dashboard


이 수업은 CC 라이센스를 따르고 있으며, 아래 링크 에서도 볼 수 있습니다.
Node.js-Express https://opentutorials.org/course/3370
쿠키와 인증 https://opentutorials.org/course/3387
세션과 인증 https://opentutorials.org/course/3400 passpor.js
https://opentutorials.org/course/3402
다중 사용자 https://opentutorials.org/course/3411 google login https://opentutorials.org/course/3413 facebook login
https://opentutorials.org/course/3414
소스 : https://github.com/braverokmc79/Nodejs-master-passpot-google
$ git clone https://github.com/passport/todos-express-starter.git google-tutorial
Google Login
80. 수업소개
3
81. 프로젝트 등록
82. Passprot.js 설정
83.Resource Owner 승인절차
84.id 획득 절차
85.사용자 추가
86.수업을 마치며
Passport 사이트 공식 문서 구글 연동 :
1) https://www.passportjs.org/tutorials/google/
2) https://www.passportjs.org/packages/passport-google-oauth/
$ npm install passport-google-oauth $ npm install passport-google-oidc
앱 등록
이전 섹션 에서 사용자가 "Google로 로그인"을 클릭할 때 Google로 리디렉션하는 경로를 추가했습니다. 이 섹션에서는 Google API를 사용할 수 있도록 앱을 Google에 등록합니다.
Google Cloud Platform 콘솔 로 이동합니다 .
프로젝트 목록에서 프로젝트를 선택하거나 새 프로젝트를 만듭니다.
API 및 서비스 페이지 로 이동하여 자격 증명 을 선택 합니다.
기존 애플리케이션이 있는 경우 OAuth 2.0 클라이언트 ID 아래에 나열됩니다 . OAuth 클라이언트 편집 을 클릭 하여 클라이언트 ID와 비밀을 얻고 전략 구성 을 진행합니다 . 그렇지 않으면 계속하십시오.
아직 구성 하지 않은 경우 OAuth 동의 화면 을 구성 합니다 . Google 계정이 있는 모든 사용자가 애플리케이션을 사용할 수 있도록 하려면 외부 를 선택 합니다. 앱 이름, 지원 이메일 및 개발자 연락처 정보를 입력하여 앱 등록 프로세스를 완료합니다.
자격 증명 만들기 를 클릭 한 다음 OAuth 클라이언트 ID 를 선택 합니다.
응용 프로그램 유형 으로 웹 응용 프로그램 을 선택 합니다 .
승인된 리디렉션 URI 에서 URI 추가 를 클릭 합니다. 를 입력하십시오 .http://localhost:3000/oauth2/redirect/google
만들기 를 클릭 하여 OAuth 클라이언트를 만듭니다. 다음 화면에는 클라이언트 ID와 암호가 표시됩니다. 전략 구성 을 진행합니다 .
환경변수 설정
$ npm install dotenv
const dotenv = require("dotenv")
dotenv.config();
console.log("환경변수 :", process.env['GOOGLE_CLIENT_ID']);
.env
$ touch .env
GOOGLE_CLIENT_ID=__INSERT_CLIENT_ID_HERE__ GOOGLE_CLIENT_SECRET=__INSERT_CLIENT_SECRET_HERE__
DB 테이블 생성
CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(30), `email` varchar(30) unique NOT NULL, `hashed_password` varchar(100) DEFAULT NULL, `salt` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) )ENGINE=InnoDB 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; CREATE TABLE `topics` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL, `description` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, `created` datetime DEFAULT current_timestamp(), `user_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), FOREIGN KEY (user_id) REFERENCES users(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

1)main.js
const express = require('express')
const fs = require('fs');
const bodyParser = require('body-parser')
const compression = require('compression')
const logger = require('morgan');
const session = require('express-session')
const MySQLStore = require('express-mysql-session')(session);
const flash = require('connect-flash');
const app = express()
const port = 3000
const helmet = require('helmet')
const dotenv = require("dotenv")
dotenv.config();
app.use(helmet())
app.use(express.static('public'))
app.use(bodyParser.urlencoded({ extended: false }));
app.use(compression());
// 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);
app.get('*', (req, res, next) => {
fs.readdir('./data', function (error, filelist) {
req.list = filelist;
next();
});
});
const indexRouter = require("./routes/index");
const authRouter = require("./routes/auth")(passport);
const topicRouter = require("./routes/topic");
app.use("/", indexRouter);
app.use('/auth', authRouter);
app.use('/topic', topicRouter);
app.use(function (req, res) {
res.status(400).send("Sorry cant find that!");
});
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
2)routes/auth.js
const express = require('express');
const router = express.Router();
const template = require('../lib/template.js');
const auth = require("../lib/auth.js");
const db = require("../lib/db");
global.crypto = require('crypto')
module.exports = function (passport) {
//로그인 페이지
router.get("/login", (req, res) => {
const fmsg = req.flash();
let feedback = '';
if (fmsg.error) {
feedback = fmsg.error[0];
}
const express = require('express');
const router = express.Router();
const template = require('../lib/template.js');
const auth = require("../lib/auth.js");
const db = require("../lib/db");
global.crypto = require('crypto')
module.exports = function (passport) {
//로그인 페이지
router.get("/login", (req, res) => {
const fmsg = req.flash();
let feedback = '';
if (fmsg.error) {
feedback = fmsg.error[0];
}
const title = 'WEB - login';
const list = template.list(req.list);
const html = template.HTML(title, list, `
<div style="color:red">${feedback}</div>
<form action="/auth/login_process" method="post">
<p><input type="text" name="email" placeholder="email"></p>
<p><input type="password" name="password" placeholder="password"></p>
<p>
<input type="submit" value="login">
</p>
<p>
<a class="button google" style="padding: 8px; text-decoration: none; color: #fff;background: #a22f2f;border-radius: 50px;"
href="/auth/login/federated/google">Sign in with Google</a>
</p>
</form>
`, '', auth.statusUI(req, res));
res.send(html);
});
// 로그인 처리 - 1) 성공 및 실패 페이지 설정 및 flash 사용여부 설정하기
router.post('/login_process', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/auth/login',
failureFlash: true,
successFlash: true
}));
//1.구글로그인 처리 - 로그인 버튼 클릭시
router.get('/login/federated/google', passport.authenticate('google'));
//2.구글로그인 처리 - 콜백 반환
router.get('/oauth2/redirect/google', passport.authenticate('google', {
successRedirect: '/',
failureRedirect: '/auth/login',
failureFlash: true,
successFlash: true
}));
//로그 아웃 처리
router.get('/logout', function (req, res, next) {
req.logout(function (err) {
if (err) { return next(err); }
req.session.destroy(function (err) {
res.redirect("/");
})
});
});
//회원가입페이지
router.get('/signup', function (req, res, next) {
const fmsg = req.flash();
let feedback = '';
console.log("회원 가입페이지 : ", fmsg);
if (fmsg.error) {
feedback = fmsg.error[0];
}
const title = 'WEB - login';
const list = template.list(req.list);
const html = template.HTML(title, list, `
<div style="color:red">${feedback}</div>
<h1>Sign up</h1>
<form action="/auth/signup_process" method="post">
<section>
<label for="username">이름</label>
<input id="username" name="username" type="text" placeholder="이름" required>
</section>
<section>
<label for="email">이메일</label>
<input id="email" name="email" type="email" placeholder="이메일" required>
</section>
<section>
<label for="password">비밀번호</label>
<input id="password" name="password" type="password" autocomplete="new-password" placeholder="비밀번호" required>
</section>
<section>
<label for="password">비밀번호확인</label>
<input id="password" name="pw2" type="password" autocomplete="new-password" placeholder="비밀번호확인" required>
</section>
<button type="submit">Sign up</button>
</form>
`, '', auth.statusUI(req, res));
res.send(html);
});
//회원가입처리
router.post('/signup_process', function (req, res, next) {
if (req.body.password !== req.body.pw2) {
req.flash("error", "비밀번호가 일치 하지 않습니다.");
return res.redirect("/auth/signup");
}
crypto.randomBytes(16, (error, buf) => {
const salt = buf.toString("base64");
crypto.pbkdf2(req.body.password.trim(), salt, 310000, 32, 'sha256', function (err, hashedPassword) {
console.log("회원가입 데이터 : ", req.body.password, salt, hashedPassword.toString('hex'));
if (err) { return next(err); }
db.query('INSERT INTO users (username,email, hashed_password, salt) VALUES (?, ?, ?, ?)', [
req.body.username,
req.body.email,
hashedPassword.toString('hex'),
salt
], function (err, results, fields) {
if (err) {
req.flash("error", err.toString());
return res.redirect('/auth/signup');
}
var user = {
id: results.insertId,
username: req.body.username
};
console.log("등록한 insertId :", results.insertId);
req.login(user, function (err) {
if (err) { return next(err); }
req.flash("success", "회원가입을 축하합니다.");
res.redirect('/');
});
});
});
});
});
return router;
}
const title = 'WEB - login';
const list = template.list(req.list);
const html = template.HTML(title, list, `
<div style="color:red">${feedback}</div>
<form action="/auth/login_process" method="post">
<p><input type="text" name="username" placeholder="username"></p>
<p><input type="password" name="password" placeholder="password"></p>
<p>
<input type="submit" value="login">
</p>
<p>
<a class="button google" href="/auth/login/federated/google">Sign in with Google</a>
</p>
</form>
`, '', auth.statusUI(req, res));
res.send(html);
});
//1.구글로그인 처리 - 로그인 버튼 클릭시
router.get('/login/federated/google', passport.authenticate('google'));
//2.구글로그인 처리 - 콜백 반환
router.get('/oauth2/redirect/google', passport.authenticate('google', {
successRedirect: '/',
failureRedirect: '/auth/login'
}));
//로그 아웃 처리
router.get('/logout', function (req, res, next) {
req.logout(function (err) {
if (err) { return next(err); }
req.session.destroy(function (err) {
res.redirect("/");
})
});
});
~
return router;
}
★현재 프로젝트에서는 적용하지 않았으나,
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);
}
));
3) lib/passport.js
const passport = require("passport");
const crypto = require('crypto');
const db = require("../lib/db");
const LocalStrategy = require('passport-local');
const GoogleStrategy = require('passport-google-oidc');
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, email: 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: '등록된 email 이 없습니다.' }); }
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 GoogleStrategy({
clientID: process.env['GOOGLE_CLIENT_ID'],
clientSecret: process.env['GOOGLE_CLIENT_SECRET'],
callbackURL: '/auth/oauth2/redirect/google',
scope: ['profile', 'email']
}, function verify(issuer, profile, cb) {
console.log("1.구글 로그인 profile: ", profile);
db.query('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [
'Google',
profile.id
], function (err, row) {
if (err) { return cb(err); }
console.log("2. 구글 로그인 등록 처리: ", row.length);
//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, 'Google', profile.id], function (err) {
if (err) { return cb(err); }
return cb(null, results[0]);
});
} else {
//2) 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, "Google", profile.id
], function (err) {
if (err) { return cb(err); }
const user = {
id: id,
username: profile.displayName
};
return cb(null, user);
});
});
}
});
} else {
//2.federated_credentials 테이블 등록되어 있다면 바로 로그인처리
console.log("row.user_id : ", row[0].user_id);
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;
}
기타 참조
lib/db.js
const mysql = require('mysql');
const db = mysql.createConnection({
host: 'localhost',
user: 'opentutorials',
password: '1111',
database: 'opentutorials'
});
db.connect();
module.exports = db;
lib/auth.js
module.exports = {
isOwner: function (req, res) {
console.log(" isOwner req.user : ", req.user);
if (req.user) return true;
else return false;
},
statusUI: function (req, res) {
let authStatusUI = '<a href="/auth/login">login</a> <a href="/auth/signup">signup</a>';
if (this.isOwner(req, res)) {
authStatusUI = `${req.user.username} <a href="/auth/logout">logout</a>
`;
}
return authStatusUI;
}
}














댓글 ( 4)
댓글 남기기