Thứ năm, 24/09/2020 | 00:00 GMT+7

Cách triển khai xác thực API bằng mã thông báo web JSON và hộ chiếu

Nhiều ứng dụng web và API sử dụng hình thức xác thực để bảo vệ tài nguyên và chỉ giới hạn quyền truy cập của chúng đối với những user đã được xác minh.

Mã thông báo web JSON (JWT) là một tiêu chuẩn mở định nghĩa một cách nhỏ gọn và khép kín để truyền thông tin an toàn giữa các bên dưới dạng đối tượng JSON.

Hướng dẫn này sẽ hướng dẫn bạn cách triển khai xác thực cho một API bằng JWT và Passport , một phần mềm trung gian xác thực cho Node .

Dưới đây là tổng quan ngắn gọn về ứng dụng bạn sẽ xây dựng:

  • User đăng ký và một account user được tạo.
  • User đăng nhập và mã thông báo web JSON được chỉ định cho user .
  • Mã thông báo này được gửi bởi user khi cố gắng truy cập các tuyến đường an toàn nhất định.
  • Khi mã thông báo đã được xác minh, user sau đó được phép truy cập vào tuyến đường.

Yêu cầu

Để hoàn thành hướng dẫn này, bạn cần :

Hướng dẫn này đã được xác minh với Node v14.2.0, npm v6.14.5, và mongodb-community v4.2.6.

Bước 1 - Cài đặt dự án

Hãy bắt đầu bằng cách cài đặt dự án. Trong cửa sổ terminal của bạn, hãy tạo một folder cho dự án:

  • mkdir jwt-and-passport-auth

Và chuyển đến folder mới đó:

  • cd jwt-and-passport-auth

Tiếp theo, khởi tạo một package.json mới:

  • npm init -y

Cài đặt các phụ thuộc dự án:

  • npm install --save bcrypt@4.0.1 body-parser@1.19.0 express@4.17.1 jsonwebtoken@8.5.1 mongoose@5.9.15 passport@0.4.1 passport-jwt@4.0.0 passport-local@1.0.0

Bạn cần bcrypt để băm password user , jsonwebtoken để ký mã thông báo, passport-local để thực hiện chiến lược local và passport-jwt để truy xuất và xác minh JWT.

Cảnh báo : Khi chạy cài đặt, bạn có thể gặp sự cố với bcrypt tùy thuộc vào version Node bạn đang chạy.

Tham khảo README để xác định khả năng tương thích với môi trường của bạn.

Đến đây, dự án của bạn đã được khởi tạo và tất cả các phụ thuộc đã được cài đặt. Tiếp theo, bạn sẽ thêm một database để lưu trữ thông tin user .

Bước 2 - Cài đặt database

Một lược đồ database cài đặt các kiểu dữ liệu và cấu trúc của database . Cơ sở dữ liệu sẽ yêu cầu một schemas cho user .

Tạo một folder model :

  • mkdir model

Tạo file model.js trong folder mới này:

  • nano model/model.js

Thư viện mongoose được sử dụng để xác định một schemas được ánh xạ tới bộ sưu tập MongoDB. Trong schemas , một email và password sẽ được yêu cầu cho user . Thư viện mongoose lấy schemas và chuyển đổi nó thành một mô hình:

model / model.js
const mongoose = require('mongoose');  const Schema = mongoose.Schema;  const UserSchema = new Schema({   email: {     type: String,     required: true,     unique: true   },   password: {     type: String,     required: true   } });  const UserModel = mongoose.model('user', UserSchema);  module.exports = UserModel; 

Bạn nên tránh lưu trữ password dưới dạng văn bản thuần túy vì nếu kẻ tấn công quản lý để truy cập vào database , password có thể bị đọc.

Để tránh điều này, bạn sẽ sử dụng một gói có tên bcrypt để băm password user và lưu trữ chúng một cách an toàn. Thêm thư viện và các dòng mã sau:

model / model.js
// ...  const bcrypt = require('bcrypt');  // ...  const UserSchema = new Schema({   // ... });  UserSchema.pre(   'save',   async function(next) {     const user = this;     const hash = await bcrypt.hash(this.password, 10);      this.password = hash;     next();   } );  // ...  module.exports = UserModel; 

Mã trong hàm UserScheme.pre() được gọi là pre-hook. Trước khi thông tin user được lưu trong database , hàm này sẽ được gọi, bạn sẽ nhận được password văn bản thuần túy, băm nó và lưu trữ nó.

this đề cập đến tài liệu hiện tại sắp được lưu.

await bcrypt.hash(this.password, 10) chuyển password và giá trị của vòng muối (hoặc chi phí ) thành 10 . Chi phí cao hơn sẽ chạy băm nhiều lần hơn và an toàn hơn. Nó có sự đánh đổi của việc chuyên sâu hơn về mặt tính toán đến mức có thể ảnh hưởng đến hiệu suất ứng dụng của bạn.

Tiếp theo, bạn thay password văn bản thuần túy bằng mã băm rồi lưu trữ.

Cuối cùng, bạn cho biết bạn đã hoàn tất và nên chuyển sang phần mềm trung gian next() với next() .

Bạn cũng cần đảm bảo user đang cố gắng đăng nhập có thông tin xác thực chính xác. Thêm phương thức mới sau:

model / model.js
// ...  const UserSchema = new Schema({   // ... });  UserSchema.pre(   // ... });  UserSchema.methods.isValidPassword = async function(password) {   const user = this;   const compare = await bcrypt.compare(password, user.password);    return compare; }  // ...  module.exports = UserModel; 

bcrypt băm password do user gửi để đăng nhập và kiểm tra xem password băm được lưu trữ trong database có trùng với password được gửi hay không. Nó sẽ trả về true nếu có một trận đấu. Ngược lại, nó sẽ trả về false nếu không có kết quả phù hợp.

Đến đây, bạn có một schemas và mô hình được xác định cho bộ sưu tập MongoDB của bạn .

Bước 3 - Cài đặt phần mềm đăng ký và đăng nhập

Passport là một phần mềm trung gian xác thực được sử dụng để xác thực các yêu cầu.

Nó cho phép các nhà phát triển sử dụng các chiến lược khác nhau để xác thực user , chẳng hạn như sử dụng database local hoặc kết nối với mạng xã hội thông qua các API.

Trong bước này, bạn sẽ sử dụng chiến lược local (email và password ).

Bạn sẽ sử dụng chiến lược passport-local để tạo phần mềm trung gian xử lý việc đăng ký và đăng nhập của user . Điều này sau đó sẽ được cắm vào các tuyến đường nhất định và được sử dụng để xác thực.

Tạo folder auth :

  • mkdir auth

Tạo file auth.js trong folder mới này:

  • nano auth/auth.js

Bắt đầu bằng cách yêu cầu passport , passport-localUserModel đã được tạo ở bước trước:

auth / auth.js
const passport = require('passport'); const localStrategy = require('passport-local').Strategy; const UserModel = require('../model/model'); 

Đầu tiên, thêm phần mềm trung gian Passport để xử lý đăng ký của user :

auth / auth.js
// ...  passport.use(   'signup',   new localStrategy(     {       usernameField: 'email',       passwordField: 'password'     },     async (email, password, done) => {       try {         const user = await UserModel.create({ email, password });          return done(null, user);       } catch (error) {         done(error);       }     }   ) ); 

Đoạn mã này lưu thông tin do user cung cấp vào database , sau đó gửi thông tin user đến phần mềm trung gian tiếp theo nếu thành công.

Nếu không, nó báo lỗi.

Tiếp theo, thêm phần mềm trung gian Passport để xử lý đăng nhập của user :

auth / auth.js
// ...  passport.use(   'login',   new localStrategy(     {       usernameField: 'email',       passwordField: 'password'     },     async (email, password, done) => {       try {         const user = await UserModel.findOne({ email });          if (!user) {           return done(null, false, { message: 'User not found' });         }          const validate = await user.isValidPassword(password);          if (!validate) {           return done(null, false, { message: 'Wrong Password' });         }          return done(null, user, { message: 'Logged in Successfully' });       } catch (error) {         return done(error);       }     }   ) ); 

Mã này tìm thấy một user được liên kết với email được cung cấp.

  • Nếu user không trùng với mọi user trong database , nó sẽ trả về lỗi "User not found" .
  • Nếu password không trùng với password được liên kết với user trong database , nó sẽ trả về lỗi "Wrong Password" .
  • Nếu user và password trùng khớp, nó sẽ trả về thông báo "Logged in Successfully" và thông tin user được gửi đến phần mềm trung gian tiếp theo.

Nếu không, nó báo lỗi.

Đến đây, bạn có một phần mềm trung gian để xử lý việc đăng ký và đăng nhập.

Bước 4 - Tạo Điểm cuối Đăng ký

Express là một khung công tác web cung cấp định tuyến. Trong bước này, bạn sẽ tạo một tuyến đường cho một điểm cuối signup .

Tạo folder routes :

  • mkdir routes

Tạo file routes.js trong folder mới này:

  • nano routes/routes.js

Bắt đầu bằng cách yêu cầu cấp expresspassport :

tuyến đường / tuyến đường.js
const express = require('express'); const passport = require('passport');  const router = express.Router();  module.exports = router; 

Tiếp theo, thêm xử lý yêu cầu ĐĂNG để signup :

tuyến đường / tuyến đường.js
// ...  const router = express.Router();  router.post(   '/signup',   passport.authenticate('signup', { session: false }),   async (req, res, next) => {     res.json({       message: 'Signup successful',       user: req.user     });   } );  module.exports = router; 

Khi user gửi yêu cầu POST đến tuyến đường này, Passport sẽ xác thực user dựa trên phần mềm trung gian đã tạo trước đó.

Đến đây bạn có một điểm cuối signup . Tiếp theo, bạn cần một điểm cuối login .

Bước 5 - Tạo Điểm cuối Đăng nhập và Ký JWT

Khi user đăng nhập, thông tin user được chuyển tới lệnh gọi lại tùy chỉnh của bạn, từ đó tạo ra mã thông báo an toàn với thông tin.

Trong bước này, bạn sẽ tạo một lộ trình cho một điểm cuối login .

Đầu tiên, yêu cầu jsonwebtoken :

tuyến đường / tuyến đường.js
const express = require('express'); const passport = require('passport'); const jwt = require('jsonwebtoken');  // ... 

Tiếp theo, thêm xử lý yêu cầu POST để login :

tuyến đường / tuyến đường.js
// ...  const router = express.Router();  // ...  router.post(   '/login',   async (req, res, next) => {     passport.authenticate(       'login',       async (err, user, info) => {         try {           if (err || !user) {             const error = new Error('An error occurred.');              return next(error);           }            req.login(             user,             { session: false },             async (error) => {               if (error) return next(error);                const body = { _id: user._id, email: user.email };               const token = jwt.sign({ user: body }, 'TOP_SECRET');                return res.json({ token });             }           );         } catch (error) {           return next(error);         }       }     )(req, res, next);   } );  module.exports = router; 

Bạn không nên lưu trữ thông tin nhạy cảm như password của user trong mã thông báo.

Bạn lưu trữ idemail trong tải trọng của JWT. Sau đó, bạn ký mã thông báo bằng bí mật hoặc khóa ( TOP_SECRET ). Cuối cùng, bạn gửi lại mã thông báo cho user .

Lưu ý: Bạn đặt { session: false } vì bạn không muốn lưu trữ chi tiết user trong một phiên. Bạn mong đợi user gửi mã thông báo theo từng yêu cầu đến các tuyến an toàn.

Điều này đặc biệt hữu ích cho các API, nhưng nó không phải là cách tiếp cận được khuyến khích cho các ứng dụng web vì lý do hiệu suất.

Đến đây bạn có một điểm cuối login . User đã đăng nhập thành công sẽ tạo mã thông báo. Tuy nhiên, ứng dụng của bạn chưa thực hiện bất kỳ điều gì với mã thông báo.

Bước 6 - Xác minh JWT

Vì vậy, bây giờ bạn đã xử lý việc đăng ký và đăng nhập của user , bước tiếp theo là cho phép user có mã thông báo truy cập vào các tuyến an toàn nhất định.

Trong bước này, bạn sẽ xác minh các mã thông báo chưa bị thao túng và hợp lệ.

Xem lại file auth.js :

  • nano auth/auth.js

Thêm các dòng mã sau:

auth / auth.js
// ...  const JWTstrategy = require('passport-jwt').Strategy; const ExtractJWT = require('passport-jwt').ExtractJwt;  passport.use(   new JWTstrategy(     {       secretOrKey: 'TOP_SECRET',       jwtFromRequest: ExtractJWT.fromUrlQueryParameter('secret_token')     },     async (token, done) => {       try {         return done(null, token.user);       } catch (error) {         done(error);       }     }   ) ); 

Mã này sử dụng passport-jwt để extract JWT từ tham số truy vấn. Sau đó, nó xác minh mã thông báo này đã được ký bằng khóa hoặc khóa bí mật trong quá trình đăng nhập ( TOP_SECRET ). Nếu mã thông báo hợp lệ, chi tiết user sẽ được chuyển đến phần mềm trung gian tiếp theo.

Lưu ý: Nếu bạn cần các chi tiết bổ sung hoặc nhạy cảm về user không có trong mã thông báo, bạn có thể sử dụng _id có sẵn trên mã thông báo để truy xuất chúng từ database .

Ứng dụng của bạn hiện có khả năng vừa ký mã thông báo vừa xác minh chúng.

Bước 7 - Tạo các tuyến đường an toàn

Bây giờ, hãy tạo một số tuyến an toàn mà chỉ những user có mã thông báo đã được xác minh mới có thể truy cập.

Tạo một file secure-routes.js :

  • nano routes/secure-routes.js

Tiếp theo, thêm các dòng mã sau:

tuyến đường / an toàn-tuyến đường.js
const express = require('express'); const router = express.Router();  router.get(   '/profile',   (req, res, next) => {     res.json({       message: 'You made it to the secure route',       user: req.user,       token: req.query.secret_token     })   } );  module.exports = router; 

Mã này xử lý một yêu cầu GET cho profile . Nó trả về thông báo "You made it to the secure route" . Nó cũng trả về thông tin về usertoken .

Mục tiêu là để chỉ những user có mã thông báo đã xác minh mới được nhận phản hồi này.

Bước 8 - Kết hợp tất cả lại với nhau

Vì vậy, bây giờ bạn đã hoàn tất việc tạo các tuyến đường và phần mềm trung gian xác thực, bạn có thể kết hợp mọi thứ lại với nhau.

Tạo file app.js mới:

  • nano app.js

Tiếp theo, thêm mã sau:

app.js
const express = require('express'); const mongoose = require('mongoose'); const passport = require('passport'); const bodyParser = require('body-parser');  const UserModel = require('./model/model');  mongoose.connect('mongodb://127.0.0.1:27017/passport-jwt', { useMongoClient: true }); mongoose.connection.on('error', error => console.log(error) ); mongoose.Promise = global.Promise;  require('./auth/auth');  const routes = require('./routes/routes'); const secureRoute = require('./routes/secure-routes');  const app = express();  app.use(bodyParser.urlencoded({ extended: false }));  app.use('/', routes);  // Plug in the JWT strategy as a middleware so only verified users can access this route. app.use('/user', passport.authenticate('jwt', { session: false }), secureRoute);  // Handle errors. app.use(function(err, req, res, next) {   res.status(err.status || 500);   res.json({ error: err }); });  app.listen(3000, () => {   console.log('Server started.') }); 

Lưu ý: Tùy thuộc vào version mongoose của bạn, bạn có thể gặp phải thông báo sau: WARNING: The 'useMongoClient' option is no longer necessary in mongoose 5.x, please remove it. .

Bạn cũng có thể gặp phải thông báo cho deprecation useNewUrlParser , useUnifiedTopology , và ensureIndex ( createIndexes ).

Trong quá trình khắc phục sự cố, ta có thể giải quyết những vấn đề này bằng cách sửa đổi lệnh gọi phương thức mongoose.connect và thêm lệnh gọi phương thức mongoose.set :

mongoose.connect("mongodb://127.0.0.1:27017/passport-jwt", {   useNewUrlParser: true,   useUnifiedTopology: true, }); mongoose.set("useCreateIndex", true); 

Chạy ứng dụng của bạn bằng lệnh sau:

  • node app.js

Bạn sẽ thấy thông báo "Server started." thông điệp. Để ứng dụng chạy để kiểm tra nó.

Bước 9 - Kiểm tra với Postman

Đến đây bạn đã tổng hợp mọi thứ lại với nhau, bạn có thể sử dụng Postman để kiểm tra xác thực API của bạn .

Lưu ý: Nếu bạn cần hỗ trợ chuyển giao diện Người đưa thư cho các yêu cầu, hãy tham khảo tài liệu chính thức .

Đầu tiên, bạn sẽ phải đăng ký một user mới trong ứng dụng của bạn bằng email và password .

Trong Postman, hãy cài đặt yêu cầu tới điểm cuối signup mà bạn đã tạo trong routes.js :

POST localhost:3000/signup Body x-www-form-urlencoded 

Và gửi những thông tin chi tiết này qua Body dung yêu cầu của bạn:

Key Giá trị
e-mail example@example.com
password password

Khi hoàn tất, hãy nhấp vào nút Gửi để bắt đầu yêu cầu POST :

Output
{ "message": "Signup successful", "user": { "_id": "[a long string of characters representing a unique id]", "email": "example@example.com", "password": "[a long string of characters representing an encrypted password]", "__v": 0 } }

Mật khẩu của bạn hiển thị dưới dạng một chuỗi được mã hóa vì đây là cách nó được lưu trữ trong database . Đây là kết quả của dấu móc trước bạn đã viết trong model.js để sử dụng bcrypt để băm password .

Bây giờ, đăng nhập bằng thông tin đăng nhập và nhận mã thông báo của bạn.

Trong Postman, hãy cài đặt yêu cầu tới điểm cuối login mà bạn đã tạo trong routes.js :

POST localhost:3000/login Body x-www-form-urlencoded 

Và gửi những thông tin chi tiết này qua Body dung yêu cầu của bạn:

Key Giá trị
e-mail example@example.com
password password

Khi hoàn tất, hãy nhấp vào nút Gửi để bắt đầu yêu cầu POST :

Output
{ "token": "[a long string of characters representing a token]" }

Đến đây bạn đã có mã thông báo của bạn , bạn sẽ gửi mã thông báo này khi nào bạn muốn truy cập một tuyến đường an toàn. Copy paste nó để sử dụng sau này.

Bạn có thể kiểm tra cách ứng dụng của bạn xử lý mã thông báo xác minh bằng cách truy cập /user/profile .

Trong Postman, hãy cài đặt yêu cầu tới điểm cuối profile mà bạn đã tạo trong secure-routes.js :

GET localhost:3000/user/profile Params 

Và chuyển mã thông báo của bạn vào một tham số truy vấn có tên là secret_token :

Key Giá trị
secret_token [a long string of characters representing a token]

Khi hoàn tất, hãy nhấp vào nút Gửi để bắt đầu yêu cầu GET :

Output
{ "message": "You made it to the secure route", "user": { "_id": "[a long string of characters representing a unique id]", "email": "example@example.com" }, "token": "[a long string of characters representing a token]" }

Mã thông báo sẽ được thu thập và xác minh. Nếu mã thông báo hợp lệ, bạn sẽ được cấp quyền truy cập vào tuyến đường an toàn. Đây là kết quả của phản hồi bạn đã tạo trong secure-routes.js .

Bạn cũng có thể thử truy cập vào tuyến đường này, nhưng với mã thông báo không hợp lệ, yêu cầu sẽ trả về lỗi Unauthorized .

Kết luận

Trong hướng dẫn này, bạn cài đặt xác thực API với JWT và thử nghiệm nó với Postman.

Mã thông báo web JSON cung cấp một cách an toàn để tạo xác thực cho các API. Một lớp bảo mật bổ sung có thể được thêm vào bằng cách mã hóa tất cả thông tin bên trong mã thông báo, do đó làm cho nó an toàn hơn.

Nếu bạn muốn có thêm kiến thức chuyên sâu về JWT, bạn có thể sử dụng các nguồn bổ sung sau:


Tags:

Các tin liên quan

Cách cài đặt và sử dụng GoAccess Web Log Analyzer trên Ubuntu 20.04
2020-09-15
Xây dựng ứng dụng web CRUD với Python và Flask - Phần thứ nhất
2020-09-15
Phông chữ có thể thay đổi trên web bằng CSS
2020-09-01
Làm thế nào để tạo một Web Scraper đồng thời với Puppeteer, Node.js, Docker và Kubernetes
2020-08-19
Cách tạo ứng dụng web tiến bộ với Angular
2020-07-09
Cách cài đặt Django Web Framework trên Ubuntu 20.04
2020-07-06
Cách tạo chế độ xem để phát triển web Django
2020-05-14
Cách tạo chế độ xem để phát triển web Django
2020-05-14
Cách tạo ứng dụng web bằng Flask trong Python 3
2020-04-16
Cách tạo web server trong Node.js bằng module HTTP
2020-04-10