API 授权 NodeJS





5.00/5 (5投票s)
在本篇文章中,我们将通过登录用户生成和 HTTP 操作来自定义安全令牌来保护我们的 NodeJS API。
工作原理
让我们快速了解一下这项工作将如何完成。
- 为每个 HTTP 请求生成自定义令牌。
- 通过请求头(
x-access-token
)传递它。 - 服务器从每个请求中提取令牌。
- 通过在服务器上重新生成来验证自定义令牌。
- 如果令牌匹配,系统将从数据库检查用户权限。
- 否则,系统将响应状态码 403/404。
必备组件
强烈建议阅读以下之前的文章
- 启动 NodeJS: http://www.c-sharpcorner.com/article/how-to-start-with-node-js
- NodeJS 包管理器: http://www.c-sharpcorner.com/article/node-js-package-manager-vs2017
- 基本模板: http://www.c-sharpcorner.com/article/basic-templating-using-node-js-and-express
- 了解 MEAN Stack: http://www.c-sharpcorner.com/article/learn-about-mean-stack
开始吧
之前,我们在数据库中创建了 User
表,现在我们需要创建另一个名为 “UserAuthorization/Authorization
” 的表。
CREATE TABLE [dbo].[Authorization](
[Id] [int] NOT NULL,
[UserId] [nvarchar](50) NULL,
[CanView] [bit] NULL,
[CanPost] [bit] NULL,
[CanPut] [bit] NULL,
[CanDelete] [bit] NULL,
CONSTRAINT [PK_Authorization] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, _
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
添加新的存储过程来验证用户操作。
CREATE PROCEDURE [dbo].[UserAuthorization]
@Userid NVarchar(250),
@Methodtype NVarchar(250)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT CASE WHEN result = 1 THEN 'true' ELSE 'false' END AS result FROM
(
SELECT CASE
WHEN @Methodtype = 'GET' THEN [CanView]
WHEN @Methodtype = 'POST' THEN [CanPost]
WHEN @Methodtype = 'PUT' THEN [CanPut]
WHEN @Methodtype = 'DELETE' THEN [CanDelete]
ELSE 0
END AS result
FROM [dbo].[Authorization] WHERE [UserId] = @Userid
)AUTH
END
--EXEC UserAuthorization 'Shashangka', 'GET'
GO
我们已经完成了数据库工作。我们将从我们之前的应用程序示例开始。从 GitHub 下载它,然后使用 Visual Studio 2017 打开应用程序。在我们的应用程序中,我们需要修改客户端和服务器端。首先,我们将修改客户端。
客户端
让我们先从客户端开始。在这里,我们将基于几种 HMAC SHA256 & Base64 编码的哈希过程来生成令牌。
在这里,HMAC 是哈希消息认证码,SHA256 是哈希函数。
了解更多: https://en.wikipedia.org/wiki/HMAC
令牌生成
我们需要在布局页面中添加这两个库以启用哈希/编码过程。
<script src="/javascripts/hmac-sha256.js"></script>
<script src="/javascripts/enc-base64-min.js"></script>
下面的函数在客户端生成令牌,该令牌通过每个 HTTP 请求头传递。
function generateSecurityToken(actionType, loggedUser) {
var model = {
username: loggedUser,
key: actionType,
userAgent: navigator.userAgent.replace(/ \.NET.+;/, '')
};
var message = [model.username, model.userAgent].join(':');
var hash = CryptoJS.HmacSHA256(message, model.key);
var token = CryptoJS.enc.Base64.stringify(hash);
var tokenId = [model.username, model.key].join(':');
var tokenGenerated = CryptoJS.enc.Utf8.parse([token, tokenId].join(':'));
return CryptoJS.enc.Base64.stringify(tokenGenerated);
};
在 public/javascripts 文件夹中创建一个名为 “authorization.js” 的 .js 文件,复制代码片段然后粘贴。
代码解释
从令牌生成函数中,我们可以看到哈希是通过加密哈希函数生成的。
var hash = CryptoJS.HmacSHA256(message, model.key);
这里,第一个参数是要哈希的消息,第二个参数是用于验证消息的密钥。了解更多请访问 https://code.google.com/archive/p/crypto-js。最后,令牌通过 Base64 编码类型进行编码。
Layout.html
现在像下面这样调用 “authorization.js” 文件到布局页面
<!--Token Generation-->
<script src="/javascripts/hmac-sha256.js"></script>
<script src="/javascripts/enc-base64-min.js"></script>
<script src="/javascripts/authorization.js"></script>
AngularJS 控制器
让我们修改现有的 “UserController
” 以在每个 HTTP 方法中将令牌添加到请求头。
对于 GET 方法
headers: { 'x-access-token': generateSecurityToken('GET', $scope.loggedUser)}
对于 POST 方法
headers: { 'x-access-token': generateSecurityToken('POST', $scope.loggedUser)}
对于 PUT 方法
headers: { 'x-access-token': generateSecurityToken('PUT', $scope.loggedUser)}
对于 DELETE 方法
headers: { 'x-access-token': generateSecurityToken('DELETE', $scope.loggedUser) }
最后是 UserController
templatingApp.controller('UserController', ['$scope', '$http', function ($scope, $http) {
$scope.title = "All User";
$scope.loggedUser = 'Shashangka';//Change Username according to the database
$scope.ListUser = null;
$scope.userModel = {};
$scope.userModel.Id = 0;
getallData();
//******=========Get All User=========******
function getallData() {
$http({
method: 'GET',
url: '/api/user/getAll/',
headers: { 'x-access-token': generateSecurityToken('GET', $scope.loggedUser) }
}).then(function (response) {
$scope.ListUser = response.data;
}, function (error) {
showNotif(error.data.message);
console.log(error);
});
};
//******=========Get Single User=========******
$scope.getUser = function (user) {
$http({
method: 'GET',
url: '/api/user/getUser/' + parseInt(user.Id),
headers: { 'x-access-token': generateSecurityToken('GET', $scope.loggedUser) }
}).then(function (response) {
$scope.userModel = response.data[0];
//console.log($scope.userModel);
}, function (error) {
showNotif(error.data.message);
console.log(error);
});
};
//******=========Save User=========******
$scope.saveUser = function () {
$http({
method: 'POST',
url: '/api/user/setUser/',
data: $scope.userModel,
headers: { 'x-access-token': generateSecurityToken('POST', $scope.loggedUser) }
}).then(function (response) {
showNotif("Data Saved")
$scope.reset();
getallData();
}, function (error) {
showNotif(error.data.message);
console.log(error);
});
};
//******=========Update User=========******
$scope.updateUser = function () {
$http({
method: 'PUT',
url: '/api/user/putUser/',
data: $scope.userModel,
headers: { 'x-access-token': generateSecurityToken('PUT', $scope.loggedUser) }
}).then(function (response) {
showNotif("Data Updated")
$scope.reset();
getallData();
}, function (error) {
showNotif(error.data.message);
console.log(error);
});
};
//******=========Delete User=========******
$scope.deleteUser = function (user) {
var IsConf = confirm('You are about to delete ' + user.Name + '. Are you sure?');
if (IsConf) {
$http({
method: 'DELETE',
url: '/api/user/deleteUser/' + parseInt(user.Id),
headers: { 'x-access-token': generateSecurityToken('DELETE', $scope.loggedUser) }
}).then(function (response) {
showNotif("Data Deleted")
$scope.reset();
getallData();
}, function (error) {
showNotif(error.data.message);
console.log(error);
});
}
};
//******=========Login User=========******
$scope.loginUser = function () {
$http({
method: 'POST',
url: '/api/user/login/',
data: $scope.userModel
}).then(function (response) {
$scope.reset();
}, function (error) {
console.log(error);
});
};
//******=========Clear Form=========******
$scope.reset = function () {
var msg = "Form Cleared";
$scope.userModel = {};
$scope.userModel.Id = 0;
showNotif(msg)
};
}]);
服务器端
完成客户端令牌生成并将其与 HTTP 请求一起发送后,就可以开始进行服务器端修改了。
我们需要安装 utf8 npm 包来编码/解码我们生成的令牌。
- utf8 - Node.js 的 UTF-8 编码器/解码器
要安装该包,请右键单击项目,选择“转到”>“在此处打开命令提示符”,然后键入 “npm install utf8
” 命令,然后按 Enter。
验证令牌
让我们在 data 文件夹中创建一个名为 “verifyToken.js” 的 .js 文件。在此过程中,服务器会从每个请求中提取令牌,然后执行两步验证。
- 通过在服务器上重新生成来比较自定义令牌,以及
- 在每次请求时从数据库验证用户权限
代码解释
从请求头提取令牌
var token = req.headers['x-access-token'];
在服务器端重新生成令牌以进行比较
var message = [keymodel.userid, keymodel.useragent].join(':').toString();
var sec_key = keymodel.actionType;
var servertoken = crypto.createHmac(encConfig.hType.toString(),
sec_key).update(message).digest(encConfig.eType.toString());
最后是 verifyToken.js
var utf8 = require('utf8');
var crypto = require('crypto');
var dbService = require('./dbService');
var encConfig = {
hType: "sha256",
eType: "base64",
cEnc: "ascii"
};
var validateToken = function (req, res, next) {
var token = req.headers['x-access-token'];
var userAgent = req.headers['user-agent'];
var key = utf8.encode(new Buffer(token, encConfig.eType.toString()).toString(encConfig.cEnc));
const parts = key.split(':');
var keymodel = {};
if (parts.length === 3) {
keymodel = {
clientToken: parts[0],
userid: parts[1],
actionType: parts[2],
useragent: userAgent
};
}
//Generate Token Server-Side
var message = [keymodel.userid, keymodel.useragent].join(':').toString();
var sec_key = keymodel.actionType;
var servertoken = crypto.createHmac(encConfig.hType.toString(),
sec_key).update(message).digest(encConfig.eType.toString());
//Validate Token
if (keymodel.clientToken == servertoken) {
var query = "[UserAuthorization] '" + keymodel.userid + "', '" + keymodel.actionType + "'";
dbService.executeQuery(query, function (data, err) {
if (err) {
throw err;
} else {
var response = data.recordset[0].result;
if (response == 'true') {
next();
}
else {
return res.status(403).send
({ message: 'Authorization Failed!! You are not Permitted!!' });
}
}
});
}
else {
return res.status(404).send({ message: 'Invalid Token!!' });
}
}
module.exports = validateToken;
保护 API
让我们包含 varifyToken
模块。
let verifyToken = require('../verifyToken');
现在我们需要将 verifyToken
中间件作为第二个参数添加到 express 路由中以验证令牌。
//GET API
router.get("/api/user/getAll", verifyToken, function (req, res)
//GET API
router.get("/api/user/getUser/:id", verifyToken, function (req, res)
//POST API
router.post("/api/user/setUser", verifyToken, function (req, res)
//PUT API
router.put("/api/user/putUser", verifyToken, function (req, res)
//DELETE API
router.delete("/api/user/deleteUser/:id", verifyToken, function (req, res)
了解更多: https://express.js.cn/en/guide/using-middleware.html。
最后是 APIs
var express = require('express');
var router = express.Router();
var dbService = require('../dbService');
let verifyToken = require('../verifyToken');
//GET API
router.get("/api/user/getAll", verifyToken, function (req, res) {
var query = "GetUsers";
dbService.executeQuery(query, function (data, err) {
if (err) {
throw err;
} else {
res.send(data.recordset);
}
res.end();
});
});
// GET API
router.get("/api/user/getUser/:id", verifyToken, function (req, res) {
var query = "[GetUserById] " + parseInt(req.params.id) + "";
dbService.executeQuery(query, function (data, err) {
if (err) {
throw err;
} else {
res.send(data.recordset);
}
res.end();
});
});
//POST API
router.post("/api/user/setUser", verifyToken, function (req, res) {
var query = "[SetUser] '" + req.body.Name + "', '" + req.body.Email + "', '" + req.body.Phone + "'";
dbService.executeQuery(query, function (data, err) {
if (err) {
throw err;
} else {
res.send(data.recordset);
}
res.end();
});
});
//PUT API
router.put("/api/user/putUser", verifyToken, function (req, res) {
var query = "[PutUser] " + parseInt(req.body.Id) + ", '" +
req.body.Name + "','" + req.body.Email + "', '" + req.body.Phone + "'";
dbService.executeQuery(query, function (data, err) {
if (err) {
throw err;
} else {
res.send(data.recordset);
}
res.end();
});
});
// DELETE API
router.delete("/api/user/deleteUser/:id", verifyToken, function (req, res) {
var query = "[DeleteUser] " + parseInt(req.params.id) + "";
dbService.executeQuery(query, function (data, err) {
if (err) {
throw err;
} else {
res.send(data.recordset);
}
res.end();
});
});
module.exports = router;
发布应用
转到 Visual Studio 中的任务浏览器,如下图所示
运行任务,这将把我们所有的应用程序文件复制到已发布的文件夹。
在此处打开命令提示符(Shift + 右键),然后键入 “nodemon
”。我们使用 nodemon
启动应用程序。如果我们的应用程序有任何更改,nodemon
会自动重新启动应用程序。
现在打开浏览器,输入 URL: https://:3000
输出
在下面的表格视图中,我们可以看到访问配置是由特定用户设置的。让我们根据此配置测试我们的应用程序。
删除操作
在这里,我们可以看到 “DELETE
” 请求方法被禁止,状态码为 403
。
此外,自定义响应消息显示在响应选项卡中。
更新操作
在这里,我们可以看到 “PUT
” 请求方法已成功执行,状态码为 200
。
希望这能有所帮助。:)