Vietnamese (Tiếng Việt) translation by Thai An (you can also view the original English article)
Có thể việc triển khai các chiến lược xác thực mạnh mẽ cho bất kỳ ứng dụng nào là một nhiệm vụ khó khăn và các ứng dụng Node.js cũng không ngoại lệ.
Trong hướng dẫn này, chúng tôi sẽ phát triển một ứng dụng Node.js từ đầu và sử dụng một phần mềm xác thực trung gian tương đối mới nhưng rất phổ biến - Passport để quản lý các vấn đề về xác thực của chúng tôi.
Hộ chiếu của tài liệu mô tả nó như là một
Bằng cách cung cấp chính nó như là một middleware, Passport thực hiện một công việc tuyệt vời trong việc tách các mối quan tâm khác của một ứng dụng web khỏi các nhu cầu xác thực của nó. Nhờ đó Passport có thể tích hợp & cấu hình dễ dàng vào các ứng dụng web bất kỳ trên nền Express, giống như chúng ta cấu hình các middleware khác của Express khác như logging, body-parsing, cookie-parsing, session-handling, v.v.
Hướng dẫn này giả định bạn có hiểu biết cơ bản về Node.js và Express và cố gắng tập trung vào việc xác thực, dù chúng ta tạo ra một ứng dụng Express mẫu từ đầu và tiến triển thông qua việc bổ sung các route cho nó, và xác thực một số route trong đó.
Những chiến lược xác thực
Passport mang lại cho chúng ta hơn 140 cơ chế xác thực để chọn lựa. Bạn có thể xác thực qua một giá trị của một database nội bộ hoặc từ xa hoặc sử dụng Single Sign-On (đặng nhập một lần) bằng cách sử dụng nhà cung cấp OAuth cho Facebook, Twitter, Google, để xác thực với các mạng xã hội của bạn, hoặc bạn có thể chọn lựa một danh sách mở rộng các nhà cung cấp, họ hỗ trợ xác thực với Passport và cung cấp một module node cho nó.
Nhưng đừng lo: Bạn không cần bao gồm bất kỳ chiến lược/cơ chế nào mà ứng dụng của bạn không cần. Tất cả những chiến lược này tách biệt với nhau, và được đóng gói thành những module node tách biệt, chúng không được đính kèm mặc định khi bạn cài đặt middleware của Passport: npm install passport
.
Trong hướng dẫn này, chúng tôi sẽ sử dụng Chiến lược xác thực nội bộ của Passport và xác thực người dùng dựa vào giá trị Mongo DB được cấu hình cục bộ, lưu trữ thông tin người dùng trong database. Để sử dụng Local Authentication Strategy, chúng ta cần cài đặt module passport-local: npm install passport-local
Nhưng chờ đã: trước khi bạn mở terminal và bắt đầu xử lý những lệnh này, hãy bắt đầu xây dựng một ứng dụng Express từ đầu và bổ sung vài route cho nó (để đăng nhập, đăng ký và trang chủ) và sau đó cố gắng bổ sung middleware xác thực cho nó. Lưu ý rằng chúng tôi sẽ sử dụng Express 4 phục vụ cho mục đích của hướng dẫn này, nhưng với một vài điểm khác biệt nhỏ, Passport vẫn hoạt động tốt với Express 3.
Thiết lập ứng dụng
Nếu bạn chưa thiết lập, vậy tiếp tục cài đặt Express & express generator để tạo một ứng dụng mẫu qua lệnh express passport-mongo
trong terminal. Cấu trúc ứng dụng đã tạo sẽ trông giống như sau:



Hãy loại bỏ một số chức năng mặc định mà chúng ta sẽ không sử dụng - tiếp tục xóa route users.js
và xóa tham chiếu của nó khỏi file app.js
.
Bổ sung các dependency của dự án
Mở package.json
và thêm dependencies vào passport
và mô-đun passport-local
.
"passport": "~0.2.0", "passport-local": "~1.0.0"
Khi chúng ta lưu chi tiết người dùng trong MongoDB, chúng ta sẽ sử dụng Mongoose là công cụ mô hình hoá đối tượng data. Một cách khác để cài đặt và lưu dependency vào package.json
là gõ vào:
npm install mongoose --save
package.json
sẽ giống như sau:



Bây giờ, cài đặt tất cả các dependency và chạy ứng dụng mẫu qua lệnh npm install & npm start
. Thao tác này sẽ download và cài đặt các dependency và sẽ khởi động máy chủ node. Bạn có thể kiểm tra ứng dụng Express tại http://localhost:3000 nhưng không sử dụng được nhiều đâu.
Chúng ta sẽ nhanh chóng thay đổi bằng cách tạo một ứng dụng hoàn toàn được phát triển bằng express, nó sẽ yêu cầu hiển thị một trang đăng ký cho người dùng mới, đăng nhập cho người dùng đã đăng ký, và xác thực cho người đã đăng ký bằng Passport.
Tạo model cho Mongoose
Vì chúng ta sẽ lưu các chi tiết người dùng trong Mongo, hãy tạo một User Model trong Mongoose và lưu nó trong các models/user.js
trong ứng dụng của chúng ta.
var mongoose = require('mongoose'); module.exports = mongoose.model('User',{ username: String, password: String, email: String, gender: String, address: String });
Về cơ bản, chúng tôi đang tạo ra một model của Mongoose đang sử dụng cái mà chúng tôi có thể thực hiện các hoạt động CRUD trên database nền tảng.
Định cấu hình Mongo
Nếu bạn không cài đặt Mongo cục bộ thì chúng tôi khuyên bạn nên sử dụng các dịch vụ database từ cloud như Modulus hoặc MongoLab. Việc tạo một cá thể MongoDB hoạt động bằng cách sử dụng những dịch vụ này vừa miễn phí vừa đơn giản trong vài cú click.
Sau khi bạn tạo một database bằng các dịch vụ này, bạn sẽ đươc cụng cấp một database có URI kiểu mongodb://:@novus.modulusmongo.net:27017/<dbName>
có thể sử dụng để thực hiện các hoạt động CRUD trên database. Bạn nên lưu cấu hình database trong một file riêng biệt để có thể sử dụng khi cần. Như vậy, chúng ta tạo một mô đun node db.js
giống như sau:
module.exports = { 'url' : 'mongodb://<dbuser>:<dbpassword>@novus.modulusmongo.net:27017/<dbName>' }
Nếu bạn giống tôi, đang sử dụng một cá thể Mongo cục bộ thì đã đến lúc khởi động trình tiện ích mongod
và db.js
sẽ trông giống như
module.exports = { 'url' : 'mongodb://localhost/passport' }
Bây giờ chúng ta sử dụng cấu hình này trong app.js
và kết nối với nó bằng cách sử dụng các API của Mongoose:
var dbConfig = require('./db.js'); var mongoose = require('mongoose'); mongoose.connect(dbConfig.url);
Định cấu hình Passport
Passport chỉ cung cấp cơ chế để xử lý xác thực và để lại phần xử lý session cho chúng tôi bằng cách sử dụng express-session. Mở app.js
và paste đoạn code bên dưới trước khi định cấu hình các route:
// Configuring Passport var passport = require('passport'); var expressSession = require('express-session'); app.use(expressSession({secret: 'mySecretKey'})); app.use(passport.initialize()); app.use(passport.session());
Đây là điều cần thiết vì chúng tôi muốn các session của người dùng của chúng tôi được duy trì. Trước khi chạy ứng dụng, chúng ta cần phải cài express-session làm việc và thêm nó vào danh sách dependency của chúng ta trong package.json
. Để thực hiện điều đó, hãy cài đặt npm
Mã hoá và giải mã những giá trị của người dùng
Passport cũng cần phải tmã hoá và giải mã giá trị của người dùng từ một phiên lưu trữ để hỗ trợ các phiên đăng nhập, vì thế mỗi request tiếp theo không chứa các thông tin của người dùng. Passport cung cấp 2 phương thức serializeUser
và deserializeUser
cho mục đích này.
passport.serializeUser(function(user, done) { done(null, user._id); }); passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); });
Sử dụng các chiến lược của Passport
Bây giờ chúng tôi sẽ xác định các chiến lược của Passport để xử lý login và signup. Mỗ phương thức sẽ là một giá trị của Local Authentication Strategy của Passport và sẽ được tạo ra thông qua passport.use()
hàm. Chúng tôi sử dụng connect-flash để giúp chúng tôi xử lý lỗi bằng cách cung cấp những thông điệp nhanh có thể được hiển thị lỗi cho người dùng.
Chiến lược login
Chiến lược login trông như thế này:
// passport/login.js passport.use('login', new LocalStrategy({ passReqToCallback : true }, function(req, username, password, done) { // check in mongo if a user with username exists or not User.findOne({ 'username' : username }, function(err, user) { // In case of any error, return using the done method if (err) return done(err); // Username does not exist, log error & redirect back if (!user){ console.log('User Not Found with username '+username); return done(null, false, req.flash('message', 'User Not found.')); } // User exists but wrong password, log the error if (!isValidPassword(user, password)){ console.log('Invalid Password'); return done(null, false, req.flash('message', 'Invalid Password')); } // User and password both match, return user from // done method which will be treated like success return done(null, user); } ); }));
Tham số đầu tiên cho passport.use()
là name (tên) của chiến lược sẽ được sử dụng để xác định chiến lược này khi được áp dụng sau này. Tham số thứ hai là type của chiến lược mà bạn muốn tạo, ở đây chúng tôi sử dụng username-password hoặc LocalStrategy. Cần lưu ý rằng theo mặc định, LocalStrategy sẽ muốn tìm thấy thông tin đăng nhập người dùng trong các tham số username
& password
, nhưng nó cũng cho phép chúng ta dùng bất kỳ tham số khác. Biến số cấu hình passReqToCallback
cho phép chúng ta truy xuất đối tượng request
trong hàm callback, qua đó cho phép chúng ta sử dụng bất kỳ tham số liên quan đến request.
Tiếp theo, chúng tôi sử dụng API Mongoose để tìm User trong collection cơ sở của User để kiểm tra xem người dùng đó có phải là người dùng hợp lệ hay không. Tham số cuối cùng trong hàm callback của chúng ta: done
biểu thị một phương thức hữu ích bằng cách sử dụng cái mà chúng ta có thể báo hiệu thành công hoặc thất bại cho mô-đun Passport. Để xác định định thất bại thì tham số đầu tiên phải chứa lỗi hoặc tham số thứ hai sẽ đánh giá là false
. Để biểu thị thành công tham số đầu tiên phải là null
và tham số thứ hai sẽ trả về giá trị truthy
, trong trường hợp đó nó sẽ sẵn sàng để đối tượng request
sử dụng.
Bản chất mật khẩu thường ít bảo mật, chúng ta luôn cần mã hoá chúng trước khi lưu vào cơ sở dữ liệu. Để làm điều này, ta sử dụng bcrypt-nodejs để mã hoá và giải mã các mật khẩu.
var isValidPassword = function(user, password){ return bCrypt.compareSync(password, user.password); }
Nếu bạn cảm thấy đoạn mã này khó hiểu và thích xem toàn bộ code được triển khai, cứ thoải mái xem tại đây.
Chiến lược đăng ký
Bây giờ, chúng ta định nghĩa chiến lược tiếp theo để xử lý việc đăng ký của một người dùng mới và tạo ra mục nhập dữ liệu của người đó trong Mongo DB cơ sở của chúng ta:
passport.use('signup', new LocalStrategy({ passReqToCallback : true }, function(req, username, password, done) { findOrCreateUser = function(){ // find a user in Mongo with provided username User.findOne({'username':username},function(err, user) { // In case of any error return if (err){ console.log('Error in SignUp: '+err); return done(err); } // already exists if (user) { console.log('User already exists'); return done(null, false, req.flash('message','User Already Exists')); } else { // if there is no user with that email // create the user var newUser = new User(); // set the user's local credentials newUser.username = username; newUser.password = createHash(password); newUser.email = req.param('email'); newUser.firstName = req.param('firstName'); newUser.lastName = req.param('lastName'); // save the user newUser.save(function(err) { if (err){ console.log('Error in Saving user: '+err); throw err; } console.log('User Registration succesful'); return done(null, newUser); }); } }); }; // Delay the execution of findOrCreateUser and execute // the method in the next tick of the event loop process.nextTick(findOrCreateUser); }); );
Ở đây, chúng tôi lại sử dụng Mongoose API để kiếm tra liệu username được nhập vào có tồn tại hay chưa. Nếu chưa có, sau đó sẽ tạo một người dùng mới và lưu thông tin của họ lại trong Mongo. Nếu có người đó tồn tại, sẽ trả về lỗi bằng cách dùng hàm callback done
và thông điệp nhanh. Lưu ý chúng tô dùng bcrypt-nodejs
để tạo hash cho mật khẩu trước khi lưu lại.
// Generates hash using bCrypt var createHash = function(password){ return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); }
Tạo các route
Nếu chúng ta nhìn vào tổng quan của ứng dụng, sẽ trông như bên dưới:



Bây giờ chúng ta định nghĩa các route của chúng ta cho ứng dụng trong mô-đun sau đây, nó lấy giá trị của Passport được tạo trong app.js
ở trên. Lưu mô-đun này trong routes/index.js
module.exports = function(passport){ /* GET login page. */ router.get('/', function(req, res) { // Display the Login page with any flash message, if any res.render('index', { message: req.flash('message') }); }); /* Handle Login POST */ router.post('/login', passport.authenticate('login', { successRedirect: '/home', failureRedirect: '/', failureFlash : true })); /* GET Registration Page */ router.get('/signup', function(req, res){ res.render('register',{message: req.flash('message')}); }); /* Handle Registration POST */ router.post('/signup', passport.authenticate('signup', { successRedirect: '/home', failureRedirect: '/signup', failureFlash : true })); return router; }
Phần quan trọng nhất của đoạn mã trên là việc sử dụng passport.authenticate()
để ủy quyền xác thực đến chiến lược login
và signup
khi một HTTP POST
được thực hiện cho các route /login
và /signup
tuần tự. Lưu ý rằng không bắt buộc phải đặt tên cho các chiến lược trên route và bạn có thể đặt tên bất kỳ cho nó.
Tạo view cho Jade
Tiếp theo, chúng tôi tạo 2 views sau đây cho ứng dụng của chúng tôi:
-
layout.jade
chứa bố cục cơ bản và thông tin về style -
index.jade
chứa trang login, chứa form login và cung cấp tùy chọn để tạo tài khoản mới
extends layout block content div.container div.row div.col-sm-6.col-md-4.col-md-offset-4 h1.text-center.login-title Sign in to our Passport app div.account-wall img(class='profile-img', src='https://lh5.googleusercontent.com/-b0-k99FZlyE/AAAAAAAAAAI/AAAAAAAAAAA/eu7opA4byxI/photo.jpg?sz=120') form(class='form-signin', action='/login', method='POST') input(type='text', name='username' class='form-control', placeholder='Email',required, autofocus) input(type='password', name='password' class='form-control', placeholder='Password', required) button(class='btn btn-lg btn-primary btn-block', type='submit') Sign in span.clearfix a(href='/signup', class='text-center new-account') Create an account #message if message h1.text-center.error-message #{message}
Nhờ Bootstrap, trang Login của chúng tôi bây giờ như thế này



Chúng tôi cần thêm hai views cho chi tiết đăng ký và cho trang chủ của ứng dụng:
-
register.jade
chứa form đăng ký -
home.jade
chào đón và hiển thị thông tin đăng nhập của người dùng
Nếu bạn chưa quen thuộc với Jade, hãy xem tài liệu về nó.
Triển khai chức năng Logout
Passport là một middleware, được phép bổ sung các thuộc tính và phương thức vào các yêu cầu và phản hồi và điều đó được thực hiện rất tiện lợi qua request.logout()
phương pháp làm mất hiệu lực phiên người dùng ngoài các thuộc tính khác.
/* Handle Logout */ router.get('/signout', function(req, res) { req.logout(); res.redirect('/'); });
Bảo vệ các route
Passport cũng cho phép bảo vệ việc truy xuất vào một route, mà route này được cho là không thích hợp cho người dùng chưa đăng ký. Điều này có nghĩa là nếu một số người dùng cố gắng truy cập http://localhost:3000/home mà không cần xác thực trong ứng dụng, anh ta sẽ được chuyển hướng đến trang chủ bằng cách thực hiện
/* GET Home Page */ router.get('/home', isAuthenticated, function(req, res){ res.render('home', { user: req.user }); }); // As with any middleware it is quintessential to call next() // if the user is authenticated var isAuthenticated = function (req, res, next) { if (req.isAuthenticated()) return next(); res.redirect('/'); }
Tổng kết
Passport không phải là tay chơi duy nhất trên đấu trường khi bàn về xác thực ứng dụng Node.js và vẫn có những giải pháp thay thế như EveryAuth, nhưng sự linh hoạt, hỗ trợ từ công động và sự thật là một middleware như Passport chắc chắn là một chọn lựa tốt hơn.
Đối với một so sánh chi tiết giữa hai, đây là một thú vị