在本教程中,我们将完成一个关于如何在 Node.js 中 使用 JavaScript ,并结合 JWT 认证,实现基于角色(role based)授权/访问的简单例子。
作为例子的 api 只有三个路由,以演示认证和基于角色的授权:
/users/authenticate - 接受 body 中包含用户名密码的 HTTP POST 请求的公开路由。若用户名和密码正确,则返回一个 JWT 认证令牌
/users - 只限于 "Admin" 用户访问的安全路由,接受 HTTP GET 请求;如果 HTTP 头部授权字段包含合法的 JWT 令牌,且用户在 "Admin" 角色内,则返回一个包含所有用户的列表。如果没有令牌、令牌非法或角色不符,则一个 401 Unauthorized 响应会被返回。
/users/:id - 限于通过认证的任何角色用户访问的安全路由,接受 HTTP GET 请求;如果授权成功,根据指定的 "id" 参数返回对应用户记录。注意 "Admin" 可以访问所有用户记录,而其他角色(如 "User")却只能访问其自己的记录。
教程中的项目可以在 GitHub 上找到:https://github.com/cornflourblue/node-role-based-authorization-api
从以上 URL 中下载或 clone 实验项目
运行 npm install 安装必要依赖
运行 npm start 启动 API,成功会看到 Server listening on port 4000
除了可以用 Postman 等应用直接测试 API,也可以运行一个写好的 Vue 项目查看:
下载 Vue.js 项目代码:https://github.com/cornflourblue/vue-role-based-authorization-example
运行 npm install 安装必要依赖
为了访问到我们的 Node.js 返回的数据而不是使用 Vue 项目的本地假数据,移除或注释掉 /src/index.js 文件中包含 configureFakeBackend 的两行
运行 npm start 启动应用
_helpers
authorize.js
error-handler.js
role.js
users
user.service.js
users.controller.js
config.json
server.js
项目由两个主要的子目录组成。一个是 “特性目录”( users ),另一个是 “非特性/共享组件目录”( _helpers )。
例子中目前只包含一种 users 特性,但增加其他特性也可以照猫画虎地按照同一模式组织即可。
包含了可被用于多个特性和应用其他部分的代码,并且用一个下划线前缀命名以显眼的分组它们。
const expressJwt = require('express-jwt');
const { secret } = require('config.json');
module.exports = authorize;
function authorize(roles = []) {
// 规则参数可以是一个简单字符串 (如 Role.User 或 'User')
// 也可以是数组 (如 [Role.Admin, Role.User] 或 ['Admin', 'User'])
if (typeof roles === 'string') {
roles = [roles];
}
return [
// 认证 JWT 令牌,并向请求对象附加用户 (req.user)
expressJwt({ secret }),
// 基于角色授权
(req, res, next) => {
if (roles.length && !roles.includes(req.user.role)) {
// 未授权的用户角色
return res.status(401).json({ message: 'Unauthorized' });
}
// 认证授权都齐活
next();
}
];
}
授权中间件可以被加入任意路由,以限制通过认证的某种角色用户的访问。如果角色参数留空,则对应路由会适用于任何通过验证的用户。该中间件稍后会应用在 users/users.controller.js 中。
authorize() 实际上返回了两个中间件函数。
其中的第一个( expressJwt({ secret }) )通过校验 HTTP 请求头中的 Authorization 来实现认证。认证成功时,一个 user 对象会被附加到 req 对象上,前者包含了 JWT 令牌中的数据,在本例中也就是会包含用户 id ( req.user.sub ) 和用户角色 ( req.user.role )。 sub 是 JWT 中的标准属性名,代表令牌中项目的 id。
返回的第二个中间件函数基于用户角色,检查通过认证的用户被授权的访问范围。
如果认证和授权都失败则一个 401 Unauthorized 响应会被返回。
module.exports = errorHandler;
function errorHandler(err, req, res, next) {
if (typeof (err) === 'string') {
// 自定义应用错误
return res.status(400).json({ message: err });
}
if (err.name === 'UnauthorizedError') {
// JWT 认证错误
return res.status(401).json({ message: 'Invalid Token' });
}
// 默认处理为 500 服务器错误
return res.status(500).json({ message: err.message });
}
全局错误处理逻辑用来 catch 所有错误,也能避免在应用中遍布各种冗杂的处理逻辑。它被配置为主文件 server.js 里的中间件。
module.exports = {
Admin: 'Admin',
User: 'User'
}
角色对象定义了例程中的所有角色,用起来类似枚举值,以避免传递字符串;所以可以使用 Role.Admin 而非 'Admin' 。
users 目录包含了所有特定于基于角色授权之用户特性的代码。
const config = require('config.json');
const jwt = require('jsonwebtoken');
const Role = require('_helpers/role');
// 这里简单的硬编码了用户信息,在产品环境应该存储到数据库
const users = [
{ id: 1, username: 'admin', password: 'admin', firstName: 'Admin', lastName: 'User', role: Role.Admin },
{ id: 2, username: 'user', password: 'user', firstName: 'Normal', lastName: 'User', role: Role.User }
];
module.exports = {
authenticate,
getAll,
getById
};
async function authenticate({ username, password }) {
const user = users.find(u => u.username === username && u.password === password);
if (user) {
const token = jwt.sign({ sub: user.id, role: user.role }, config.secret);
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
token
};
}
}
async function getAll() {
return users.map(u => {
const { password, ...userWithoutPassword } = u;
return userWithoutPassword;
});
}
async function getById(id) {
const user = users.find(u => u.id === parseInt(id));
if (!user) return;
const { password, ...userWithoutPassword } = user;
return userWithoutPassword;
}
用户服务模块中包含了一个认证用户凭证并返回一个 JWT 令牌的方法、一个获得应用中所有用户的方法,和一个根据 id 获取单个用户的方法。
因为要聚焦于认证和基于角色的授权,本例中硬编码了用户数组,但在产品环境中还是推荐将用户记录存储在数据库中并对密码加密。
const express = require('express');
const router = express.Router();
const userService = require('./user.service');
const authorize = require('_helpers/authorize')
const Role = require('_helpers/role');
// 路由
router.post('/authenticate', authenticate); // 公开路由
router.get('/', authorize(Role.Admin), getAll); // admin only
router.get('/:id', authorize(), getById); // 所有通过认证的用户
module.exports = router;
function authenticate(req, res, next) {
userService.authenticate(req.body)
.then(user => user
? res.json(user)
: res.status(400)
.json({ message: 'Username or password is incorrect' }))
.catch(err => next(err));
}
function getAll(req, res, next) {
userService.getAll()
.then(users => res.json(users))
.catch(err => next(err));
}
function getById(req, res, next) {
const currentUser = req.user;
const id = parseInt(req.params.id);
// 仅允许 admins 访问其他用户的记录
if (id !== currentUser.sub && currentUser.role !== Role.Admin) {
return res.status(401).json({ message: 'Unauthorized' });
}
userService.getById(req.params.id)
.then(user => user ? res.json(user) : res.sendStatus(404))
.catch(err => next(err));
}
用户控制器模块定义了所有用户的路由。使用了授权中间件的路由受约束于通过认证的用户,如果包含了角色(如 authorize(Role.Admin) )则路由受限于特定的管理员用户,否则 (e.g. authorize() ) 则路由适用于所有通过认证的用户。没有使用中间件的路由则是公开可访问的。
getById() 方法中包含一些额外的自定义授权逻辑,允许管理员用户访问其他用户的记录,但禁止普通用户这样做。
{
"secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
}
重要: "secret" 属性被 API 用来签名和校验 JWT 令牌从而实现认证,应将其更新为你自己的随机字符串以确保无人能生成一个 JWT 去对你的应用获取未授权的访问。
require('rootpath')();
const express = require('express');
const app = express();
const cors = require('cors');
const bodyParser = require('body-parser');
const errorHandler = require('_helpers/error-handler');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cors());
// api 路由
app.use('/users', require('./users/users.controller'));
// 全局错误处理
app.use(errorHandler);
// 启动服务器
const port = process.env.NODE_ENV === 'production' ? 80 : 4000;
const server = app.listen(port, function () {
console.log('Server listening on port ' + port);
});
server.js 作为 API 的主入口,配置了应用中间件、绑定了路由控制权,并启动了 Express 服务器。
原文:https://jasonwatmore.com/post/2018/11/28/nodejs-role-based-authorization-tutorial-with-example-api
js是一种弱类型的编程语言,代表着传入的变量并不清楚作为何种类型使用。对js来说传入的任意参数都应该考虑不同类型的结果,而不是单单考虑一种情况。若传入0、false等,||所要实现默认值的功能完全错误的
本文简单介绍了 YodaOS 在 API 设计过程中,如何利用 DSL,解决 YodaOS API 在多种应用形态保持一致性。以此,我们希望抛砖引玉:帮助读者更好地了解 YodaOS API 的生成过程,帮助读者了解到 DSL,也能将这种思路应用在自己的项目中
JavaScript 一直没有私有成员并不是没有原因,所以这一提议给 JavaScript 带来了新的挑战。但同时,JavaScript 在 ES2015 发布的时候已经在考虑私有化的问题了,所以要实现私有成员也并非毫无基础。
对于程序员来说,如果哪一天开始他停止了学习,那么他的职业生涯便开始宣告消亡。这不是什么危言耸听的怪语,而是一位大牛几年前告诉我的,他的信条。
随着互联网+时代的到来,移动互联网行业的发展也是突飞猛进。无论你是否承认,这个时代已经被网页所包围了,这所有一切,都是前端工程师的杰作。今天给大家聊的就是\"前端真的好学吗?\"
Web前端开发怎么入门,主要都有哪些要素组成?Web前端开发是由网页制作演变而来的,主要由HTML、CSS、JavaScript三大要素组成。专业的Web前端开发入门知识也一定会包含这些内容,下面就给大家简单介绍一下。
Highcharts 中默认开启了UTC(世界标准时间),由于中国所在时区为+8,所以经过 Highcharts 的处理后会减去8个小时。如果不想使用 UTC,有2种方法可供使用:
笔者所做的一个项目需要做一个前端的树形菜单,后端返回的数据是一个平行的list,list中的每个元素都是一个对象,例如list[0]的值为{id: 1, fid: 0, name: 一级菜单},每个元素都指定了父元素,生成的菜单可以无限级嵌套
从地址栏显示来说:forward是服务器内部重定向,客户端浏览器的网址不会发生变化;redirect发生一个状态码,告诉服务器去重新请求那个网址,显示的的新的网址数据共享:forward使用的是同一个request
使用 index 作为 key 值有什么问题呢? 在我们日常开发中我们经常会和 key 值打交道. 但是我们扪心自问, 真的理解 key 吗? 我想大多数朋友可能会有些许犹豫.
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!