前言
授權認證是每一個應用最重要的組成部分,眾所周知網際網路在安全問題方面始終處於挑戰和進化的過程中。在過去我們通常會使用Passport這樣的npm 包來解決驗證問題,儘管使用基於認證授權的session會話可以解決普通的web app開發問題,但是對於很多跨裝置、跨服務的API和擴充套件web services方面,session會話則顯然捉襟見肘。
目標
本文的目標在於快速構建一套基於Node express的API,然後我們通過POST MAN來最終跑通這個API。整個故事其實很簡單,路由分為unprotected 和 protected 兩類routes。在通過密碼和使用者名稱驗證之後,使用者會獲取到token然後儲存在客戶端,在之後的每次請求呼叫的時候都會帶著這個token。
準備工具
node和postman已經安裝完畢
構建一個簡單的server.js
在下面的程式碼中,
// =======================// get the packages we need ============// =======================var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var morgan = require('morgan');
var mongoose = require('mongoose');
var jwt = require('jsonwebtoken');
// used to create, sign, and verify tokensvar config = require('./config');
// get our config filevar User = require('./app/models/user');
// get our mongoose model// =======================// configuration =========// =======================var port = process.env.PORT || 8080;
// used to create, sign, and verify tokensmongoose.connect(config.database);
// connect to databaseapp.set('superSecret', config.secret);
// secret variable// use body parser so we can get info from POST and/or URL parametersapp.use(bodyParser.urlencoded({
extended: false
}));
app.use(bodyParser.json());
// use morgan to log requests to the consoleapp.use(morgan('dev'));
// =======================// routes ================// =======================// basic routeapp.get('/', function(req, res) {
res.send('Hello! The API is at http://localhost:' + port + '/api');
});
// API ROUTES -------------------// we'll get to these in a second// =======================// start the server ======// =======================app.listen(port);
console.log('Magic happens at http://localhost:' + port);
複製程式碼
建立一個簡單User
app.get('/setup', function(req, res) {
// create a sample user var nick = new User({
name: 'Nick Cerminara', password: 'password', admin: true
});
// save the sample user nick.save(function(err) {
if (err) throw err;
console.log('User saved successfully');
res.json({
success: true
});
});
});
複製程式碼
展示我們的User
現在我們建立我們的API routes以及以JSON格式返回所有的users. 我們將使用Expres自帶的Router()方法例項化一個apiRoutes
物件,來處理相關請求。
// API ROUTES -------------------// get an instance of the router for api routesvar apiRoutes = express.Router();
// TODO: route to authenticate a user (POST http://localhost:8080/api/authenticate)// TODO: route middleware to verify a token// route to show a random message (GET http://localhost:8080/api/)apiRoutes.get('/', function(req, res) {
res.json({
message: 'Welcome to the coolest API on earth!'
});
});
// route to return all users (GET http://localhost:8080/api/users)apiRoutes.get('/users', function(req, res) {
User.find({
}, function(err, users) {
res.json(users);
});
});
// apply the routes to our application with the prefix /apiapp.use('/api', apiRoutes);
複製程式碼
我們可以使用POSTman來對路由進行測試。
測試: http://localhost:8080/api/ ,看看能不能看到相關message內容
測試: http://localhost:8080/api/users ,檢查是否有使用者列表資料返回
授權並建立一個Token
在http://localhost:8080/api/authenticate
中登入使用者名稱和密碼,若成功則返回一個token.
之後使用者應將該token儲存客戶端的某處,比如localstorage. 然後在每一次請求時帶著這個token,在呼叫相關protected routes的時候,該router應該通過Middleware來檢查token的合法性。
var apiRoutes = express.Router();
// (POST http://localhost:8080/api/authenticate)apiRoutes.post('/authenticate', function(req, res) {
User.findOne({
name: req.body.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.json({
success: false, message: 'Authentication failed. User not found.'
});
} else if (user) {
if (user.password != req.body.password) {
res.json({
success: false, message: 'Authentication failed. Wrong password.'
});
} else {
const payload = {
admin: user.admin
};
// 如果對於客戶的要求不是很高就自定義一個token, 如果對於token有時間限制則使用jwt 外掛進行生成。 var token = jwt.sign(payload, app.get('superSecret'), {
expiresInMinutes: 1440 // expires in 24 hours
});
// 將登陸成功後的資訊攜帶token返回給客戶端 res.json({
success: true, message: 'Enjoy your token!', token: token
});
}
}
});
});
...複製程式碼
在上面的程式碼中,我們使用了jsonwebtoken
包建立生成了token,並通過mongoDB的相關包將token在資料庫進行有期限的儲存。
使用中介軟體來保護私有API
var apiRoutes = express.Router();
// 當客戶端呼叫/api路由也就apiRoutes例項化的時候,首先會被攔截進行token檢查。apiRoutes.use(function(req, res, next) {
// 從不同的地方嘗試讀取token var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token if (token) {
// 驗證token jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({
success: false, message: 'Failed to authenticate token.'
});
} else {
//如果一切順利進入路由邏輯環節 req.decoded = decoded;
next();
}
});
} else {
//如果檢查沒有通過則返回403 return res.status(403).send({
success: false, message: 'No token provided.'
});
}
});
// route to show a random message (GET http://localhost:8080/api/)...// route to return all users (GET http://localhost:8080/api/users)...// apply the routes to our application with the prefix /apiapp.use('/api', apiRoutes);
複製程式碼