如何使用JSON Web令牌(JWT)保护您的文档

article/2025/10/16 17:47:01

如何使用JSON Web令牌(JWT)保护您的文档

JWT

在本文中,我们讲解如何使用JSON网络令牌JWT来保护在线文档免受未经授权的访问,从而可以更安全的把在线文档编辑器开发集成进您自己的网络应用中去。

这里将集成开源的办公套件ONLYOFFICE Docs:

  • 文档、表格、幻灯片、表单模板编辑功能
  • 与微软Office文件格式(docx、xlsx、pptx)的高度集成
  • 实时协作

如下各图所示为文档、表格、幻灯片以及表单模板编辑功能,与微软Office高度兼容,其中第二张表格截图保留了浏览器窗口的标题栏,其它截图都为网页全屏F11模式下截取获得。
文档
表格
幻灯片
表单

第一步:创建项目框架

假定已经安装好Node.js,关于如何安装可参考这里。

为工程创建一个文件夹,打开运行下述命令:

npm init

将提示我们设置包名、版本号、license等信息,也可以直接跳过,用这些信息可以创建package.json。

然后安装express:

npm install express --save

这里需要npm的–save参数,在package.json文件中指定项目依赖于express包。

创建如下文件:

  • index.jx 启动并配置express服务器
  • app/app.js 查询处理逻辑
  • app/config.json 可变参数,例如端口号、编辑器地址等(这里使用一个json文件,但在真实项目中最好使用更可靠的方式)

index.js必须包含如下代码:

const express = require('express');
const cfg = require('./app/config.json');const app = express();app.use(express.static("public"));app.listen(cfg.port, () => {console.log(`Server is listening on ${cfg.port}`);
});

config.js文件必须包含文档编辑器的端口号:

{"port": 8080}

创建公共文件夹,添加文件index.html,向package.json文件加入如下行:

"scripts": {
"start": "node index.js"
}

如下命令启动运行app:

npm start

打开浏览器测试http://localhost:8080

第二步:打开文档

集成ONLYOFFICE的编辑器,需要安装ONLYOFFICE Document Server文档服务器。最简单的方式是使用Docker安装,仅需一行命令即可:

docker run -i -t -d -p 9090:80 onlyoffice/documentserver

文档服务器必须能够向这个服务器发送http请求,并且能接收处理服务器返回的请求。

config.json添加编辑器(文档服务器)和示例app的地址,类似如下:

"editors_root": "http://192.168.0.152:9090/",
"example_root": "http://192.168.0.152:8080/"

在这个阶段,应该向app/fileManager.js中添加文件处理的功能(获取文件、列表、文件名、扩展名等):

const fs = require('fs');
const path = require('path');const folder = path.join(__dirname, "..", "public");
const emptyDocs = path.join(folder, "emptydocs");function listFiles() {var files = fs.readdirSync(folder);var result = [];for (let i = 0; i < files.length; i++) {var stats = fs.lstatSync(path.join(folder, files[i]));if (!stats.isDirectory()) result.push(files[i])}return result;
}function exists(fileName) {return fs.existsSync(path.join(folder, fileName));
}function getDocType(fileName) {var ext = getFileExtension(fileName);if (".doc.docx.docm.dot.dotx.dotm.odt.fodt.ott.rtf.txt.html.htm.mht.pdf.djvu.fb2.epub.xps".indexOf(ext) != -1) return "text";if (".xls.xlsx.xlsm.xlt.xltx.xltm.ods.fods.ots.csv".indexOf(ext) != -1) return "spreadsheet";if (".pps.ppsx.ppsm.ppt.pptx.pptm.pot.potx.potm.odp.fodp.otp".indexOf(ext) != -1) return "presentation";return null;
}function isEditable(fileName) {var ext = getFileExtension(fileName);return ".docx.xlsx.pptx".indexOf(ext) != -1;
}function createEmptyDoc(ext) {var fileName = "new." + ext;if (!fs.existsSync(path.join(emptyDocs, fileName))) return null;var destFileName = getCorrectName(fileName);fs.copyFileSync(path.join(emptyDocs, fileName), path.join(folder, destFileName));return destFileName;
}function getCorrectName(fileName) {var baseName = getFileName(fileName, true);var ext = getFileExtension(fileName);var name = baseName + "." + ext;var index = 1;while (fs.existsSync(path.join(folder, name))) {name = baseName + " (" + index + ")." + ext;index++;}return name;
}function getFileName(fileName, withoutExtension) {if (!fileName) return "";var parts = fileName.toLowerCase().split(path.sep);fileName = parts.pop();if (withoutExtension) {fileName = fileName.substring(0, fileName.lastIndexOf("."));}return fileName;
}function getFileExtension(fileName) {if (!fileName) return null;var fileName = getFileName(fileName);var ext = fileName.toLowerCase().substring(fileName.lastIndexOf(".") + 1);return ext;
}
function getKey(fileName) {var stat = fs.statSync(path.join(folder, fileName));return new Buffer(fileName + stat.mtime.getTime()).toString("base64");
}module.exports = {listFiles: listFiles,createEmptyDoc: createEmptyDoc,exists: exists,getDocType: getDocType,getFileExtension: getFileExtension,getKey: getKey,isEditable: isEditable
}

添加pug包:

npm install pug --save

既然已经安装pug模板引擎,就可以删除index.html了。创建一个查阅文件夹,在index.js中添加下面代码连接引擎:

app.set("view engine", "pug");

然后就可以创建views/index.pug,添加创建文档、打开文档的按钮:

extends master.pugblock contentdiva(href="editors?new=docx", target="_blank")button= "Create DOCX"a(href="editors?new=xlsx", target="_blank")button= "Create XLSX"a(href="editors?new=pptx", target="_blank")button= "Create PPTX"diveach val in filesdiva(href="editors?filename=" + val, target="_blank")= val

逻辑将在app/app.js中讲解:创建一个文件(或者检查是否已经存在),然后格式化编辑器的配置,可以阅读这里查看细节,然后返回页面模板:

const fm = require('./fileManager');
const cfg = require('./config.json');function index(req, res) {res.render('index', { title: "Index", files: fm.listFiles() });
}function editors(req, res) {var fileName = "";if (req.query.new) {var ext = req.query.new;fileName = fm.createEmptyDoc(ext);} else if (req.query.filename) {fileName = req.query.filename;}if (!fileName || !fm.exists(fileName)) {res.write("can't open/create file");res.end();return;}res.render('editors', { title: fileName, api: cfg.editors_root, cfg: JSON.stringify(getEditorConfig(req, fileName)) });
}function getEditorConfig(req, fileName) {var canEdit = fm.isEditable(fileName);return {width: "100%",height: "100%",type: "desktop",documentType: fm.getDocType(fileName),document: {title: fileName,url: cfg.example_root + fileName,fileType: fm.getFileExtension(fileName),key: fm.getKey(fileName),permissions: {download: true,edit: canEdit}},editorConfig: {mode: canEdit ? "edit" : "view",lang: "en"}}
}module.exports = {index: index,editors: editors
};

在这里,加载编辑器脚本http://docserver/web-apps/apps/api/documents/api.js然后添加编辑器的实例new DocsAPI.DocEditor("iframeEditor", !{cfg})

现在运行app测试一下。

第三步:编辑文档

编辑文档,更准确的说,是保存您的修改。这需要处理从文档服务器发来的修改保存请求,在配置文件中指定如何响应这个请求,关于文档服务器的请求可以参考这里。

文档服务器发送带有JSON内容的POST请求,这就是为什么我们需要连接到中间件来从JSON解析到index.js

app.use(express.json());

为了第一时间接收它,应告诉文档服务器如何处理,向编辑器的配置文件中添加callbackUrl: cfg.example_root + "callback?filename=" + fileName

然后创建一个回调函数,从文档服务器获取信息,检查请求状态:

function callback(req, res) {try {var fileName = req.query.filename;!checkJwtToken(req);var status = req.body.status;switch (status) {case 2:case 3:fm.downloadSave(req.body.url, fileName);break;default:// to-do: process other statusesbreak;}} catch (e) {res.status(500);res.write(JSON.stringify({ error: 1, message: e.message }));res.end();return;}res.write(JSON.stringify({ error: 0 }));res.end();
}

在这个例子里,只关注文档保存的请求处理,一旦接收到保存文件请求,我们将从POST数据中获取指向我们文档的链接并将其保存到我们的文件系统中:

functiondownloadSave(downloadFrom, saveAs) {http.get(downloadFrom, (res) => {if (res.statusCode==200) {varfile=fs.createWriteStream(path.join(folder, saveAs));res.pipe(file);file.on('finish', function() {file.close();});}});
}

现在我们就有了一个具备文档编辑功能的网页应用了,接下来使用JWT来保护它免受未授权的访问。

第四步:实施JWT

ONLYOFFICE使用JSON网络令牌保护在编辑器、内部服务以及存储空间之间的数据交换。它请求一个加密的签名,然后托管在令牌中。 此令牌校验对数据执行特定操作的权限。

如果打算使用JWT最好使用准备好的包,但是在这里为了理解工作原理将完全手动实现。

入门理论基础

JWT包含三部分:

  • 头:包含元信息,例如,一个加密算法
  • 负载:数据内容
  • hash哈希:基于上面两部分和密码的哈希值

所有这三部分是JSON对象,然而JSON令牌本身是由点符号(.)所连接的所有部分的base64URL编码。

工作原理:

  1. 服务器1依据一个密钥和一个header.payload的字符串计算一个哈希值。
  2. 令牌header.payload.hash生成
  3. 服务器2接收到这个令牌,依据它的前两部分生成哈希值。
  4. 服务器2比较生成的令牌和接收到的令牌,如果匹配,那么就说明数据没有被修改

现在为这个集成实例实现JWT令牌

编辑器允许在请求包头和正文中传输JWT令牌,使用请求包正文部分比较好,因为数据包头空间有限,但是这里将考虑所有情况。

如果选择包头传输令牌,需要使用负载key密钥来将数据加入对象中。

如果选择包正文传输令牌,负载类似如下:

{
"key": "value"
}

使用包头传输令牌:

{
"payload": {
"key": "value"}
}

config.json添加key密钥:

"jwt_secret": "supersecretkey"

开启JWT启动编辑器还需要设定环境变量:

docker run -i -t -d -p 9090:80 -e JWT_ENABLED=true -e JWT_SECRET=supersecretkey onlyoffice/documentserver

如果使用包正文传输令牌,还需添加一个变量-e JWT_IN_BODY=true

docker run -i -t -d -p 9090:80 -e JWT_ENABLED=true -e JWT_SECRET=supersecretkey -e JWT_IN_BODY=true onlyoffice/documentserver

app/jwtManager.js包含JWT的所有逻辑,只需要在打开编辑器的时候向配置添加令牌:

if (jwt.isEnabled()) {
editorConfig.token=jwt.create(editorConfig);
}

令牌本身有上面理论解释的算法来计算生成,代码如下:

function create(payloadObj) {if (!isEnabled()) return null;var headerObj = {alg: "HS256",typ: "JWT"};header = b64Encode(headerObj);payload = b64Encode(payloadObj);hash = calculateHash(header, payload);return header + "." + payload + "." + hash;
}function calculateHash(header, payload) {return b64UrlEncode(crypto.createHmac("sha256", cfg.jwt_secret).update(header + "." + payload).digest("base64"));
}

这样就打开了一个文档,但也要检查一下从文档服务器接收到的令牌。

要检查包正文和包头,函数很简单,如果有问题它就会抛出错误,否则,确认了令牌后将合并包正文和令牌负载:

function checkJwtToken(req) {if (!jwt.isEnabled()) return;var token = req.body.token;var inBody = true;if (!token && req.headers.authorization) {token = req.headers.authorization.substr("Bearer ".length);inBody = false;}if (!token) throw new Error("Expected JWT token");var payload = jwt.verify(token);if (!payload) throw new Error("JWT token validation failed");if (inBody) {Object.assign(req.body, payload);} else {Object.assign(req.body, payload.payload);}
}

校验函数也很简单:

function verify(token) {if (!isEnabled()) return null;if (!token) return null;var parts = token.split(".");if (parts.length != 3) {return null;}var hash = calculateHash(parts[0], parts[1]);if (hash !== parts[2]) return null;return b64Decode(parts[1]);
}

看一下在jwtManager中的方法:

创建方法获取一个带有数据的对象,例如:

{
"key": "value"
}

创建JWT头:

{
"alg": "HS256",
"typ": "JWT"
}

然后这个方法使用这两个对象,创建JSON字符串,编码为base64url。然后用点连接这两行,基于你的key密钥生成一个hash哈希值,在这个例子里我们使用超级密钥supersecretkey。

作为结果我们得到如下令牌:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWx1ZSJ9.ozm44FMRAlWXB0PhJg935wyOkp7wtj1jXvgEGIS0iig

校验方法获得这个令牌,使用点作为分隔符拆解它,得到前两部分和后面的哈希值,然后对比自己生成的哈希值和接收到的哈希值,如果相匹配,这个负载被编码并返回JSON对象。

你也可以更深入研究令牌,学习他是如何创建的,并且寻找不同编程语言的开源库。

注意这里只是JWT的最小实现,这个标准内容很丰富,考虑了各种复杂情况,例如,令牌的有限生命周期。所以我们建议在真正实践中使用现成的JWT相关包。

我们希望这个例子能帮助你将ONLYOFFICE集成在你的网页应用中,使用JWT保护在线协同编辑功能,更多的集成示例可以在github上查阅研究,也可以在ONLYOFFICE API documentation上查找更多关于JWT实现的技术细节。

R5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWx1ZSJ9.ozm44FMRAlWXB0PhJg935wyOkp7wtj1jXvgEGIS0iig`

校验方法获得这个令牌,使用点作为分隔符拆解它,得到前两部分和后面的哈希值,然后对比自己生成的哈希值和接收到的哈希值,如果相匹配,这个负载被编码并返回JSON对象。

你也可以更深入研究令牌,学习他是如何创建的,并且寻找不同编程语言的开源库。

注意这里只是JWT的最小实现,这个标准内容很丰富,考虑了各种复杂情况,例如,令牌的有限生命周期。所以我们建议在真正实践中使用现成的JWT相关包。

我们希望这个例子能帮助你将ONLYOFFICE集成在你的网页应用中,使用JWT保护在线协同编辑功能,更多的集成示例可以在github上查阅研究,也可以在ONLYOFFICE API documentation上查找更多关于JWT实现的技术细节。


http://chatgpt.dhexx.cn/article/urFSulap.shtml

相关文章

day13 SpringBoot案例3 令牌技术、登录校验

登录校验的实现思路是怎样的&#xff1f; 项目中访问核心资源&#xff0c;通常都要进行登录校验&#xff0c;访问京东商城的订单&#xff0c;必须要先登录才能访问查询 实现思路: 可以在访问资源前进行访问的拦截&#xff0c;判断你当前会话是否有登录&#xff0c;如果没有登…

pythonjson数据解析失败_json解析失败是什么意思

因为代码的不美观等因素&#xff0c;我们会使用一些解析工具进行处理&#xff0c;有些人在解析时出现了报错。解析失败需要考虑多方面的因素&#xff0c;如格式、String类型、boolean类型等原因&#xff0c;在格式上分为格式错误和格式正确两种讨论。下面就json解析失败的原因为…

Java Web Token令牌校验

前言&#xff1a;本文只涉及JWT的理论部分&#xff0c;不涉及代码 传统的用户校验可以结合cookiesession来实现&#xff0c;但是校验信息存放在服务端&#xff0c;当用户过多时会给服务器造成太大的压力&#xff0c;所以我们可以使用token进行校验&#xff0c;将用户信息加以签…

JWT令牌生成与校验

目录 1 JWT介绍1.1 什么是JWT&#xff1f;1.2 JWT令牌结构 2 配置JWT令牌服务3 生成JWT令牌4 校验JWT令牌5 JWT整合Spring Security5.1 创建表 6 配置授权服务6.1 测试 1 JWT介绍 通过上边的测试我们发现&#xff0c;当资源服务和授权服务不在一起时资源服务使用RemoteTokenSe…

信息系统基础知识(笔记)

一、信息 1.1、信息的基本概念 信息就是信息&#xff0c;既不是物质们也不是论量&#xff0c;信息是能够用来消除不确定的东西 两个概念层次&#xff1a;本体论&#xff08;客观层&#xff0c;与课客体本身因素相关&#xff0c;与主题因素无关&#xff09; 认知论&#xff0…

计算机毕业论文内容参考|基于大数据的信息物理融合系统的分析与设计方法

文章目录 导文摘要前言绪论课题背景国内外现状与趋势:课题内容:相关技术与方法介绍:系统架构设计:数据采集与处理:数据存储与管理:数据分析与挖掘:系统优化与调试:应用场景:挑战与机遇:研究方向:系统分析:系统设计:系统实现:系统测试:总结与展望:

工业4.0中的人-信息-物理系统集成(HSI):设计与评估方法

于栖洋 译 摘要:本文概述了工业4.0中集成人和信息物理系统的设计和使用空间&#xff0c;特别关注分析、设计和评估方法和阶段的相互作用。本文首先介绍了工业4.0面临的挑战&#xff0c;综述了现有的系统设计方法&#xff0c;描述了方法模型的设计和使用空间&#xff0c;并以工…

信息系统基本知识(六)

大纲 信息系统与信息化信息系统开发方法常规信息系统集成技术软件工程新一代信息技术信息系统安全技术信息化发展与应用信息系统服务管理信息系统服务规划企业首席信息管及其责任 1.7 信息化发展与应用 我国在“十三五”规划纲要中&#xff0c;将培育人工智能、移动智能终端…

Unity物理系统(一)物理系统相关组件

一、物理系统相关组件 Unity中的物理系统涉及的组件分为如下几类&#xff1a; 刚体角色控制器碰撞体布料关节力场 二、刚体&#xff08;Rigidbody&#xff09; Rigidbody&#xff08;刚体&#xff09;组件可以使游戏对象在物理系统的控制下进行运动&#xff0c;Rigidbody …

工业信息物理系统测试验证平台(ETest_CPS)

1.产品简介 ETest_CPS是基于ETest Studio开发出的工业信息物理系统测试验证平台&#xff08;Embedded System Test Studiofor Cyber-Physical System,简称&#xff1a;ETest_CPS&#xff09;。ETest_CPS由软件和硬件组成&#xff0c;软件采用ETest&#xff0c;硬件包括测试机柜…

信息化与信息系统5

信息系统规划 大型信息系统的特点 考点&#xff1a; 具体包括以下6点&#xff1a; 规模庞大&#xff0c;跨地域性&#xff0c;网络结构复杂&#xff0c;业务种类多&#xff0c;数量大&#xff0c;用户多。 信息系统规划方法 规划流程 1. 分析企业信息化的现状 2. 制定企…

信息物理社会融合系统:一种以数据为中心的框架

信息物理社会融合系统&#xff1a;一种以数据为中心的框架 翟书颖1, 郭斌2, 李茹1, 王庭良1, 於志文2, 周兴社2 1. 西北工业大学明德学院&#xff0c;陕西 西安 710129 2. 西北工业大学计算机学院&#xff0c;陕西 西安 710129 摘要&#xff1a;信息物理社会融合系统连接信息空…

ETest_CPS——工业信息物理系统测试验证平台

1&#xff09;产品简介 ETest_CPS是一款工业信息物理系统测试验证平台&#xff08;Embedded System Test Studio for Cyber-Physical System,简称&#xff1a;ETest_CPS&#xff09;。ETest_CPS由软件和硬件组成&#xff0c;软件采用ETest&#xff0c;硬件包括测试机柜、测试主…

信息物理系统-Rijndael加密算法的实现

信息物理系统-Rijndael加密算法的实现&#xff1a; 概述&#xff1a; AES标准的Rijndael算法是一种分组加密算法&#xff0c;本次实验通过PtolemyII软件&#xff0c;实现了明文长度为128位&#xff0c;密钥长度为128位的Rijndael加密算法。 实验的完成采取自底向上的&#x…

信息化与信息系统4

信息安全概念 安全分层 信息安全分为4层&#xff1a;设备安全&#xff0c;数据安全&#xff0c;内容安全&#xff0c;行为安全 会给出某些特性&#xff0c;要求反向选择是属于哪一层。 信息系统的安全保护等级 第一级&#xff1a;对个人&#xff0c;公司造成损害 第二级&a…

物理系统(一)

物理系统主要由以下几部分组成&#xff1a;Rigidbody&#xff08;刚体&#xff09;、Character Controller&#xff08;角色控制器&#xff09;、Collider&#xff08;碰撞器&#xff09;、Cloth&#xff08;布料&#xff09;、Joint&#xff08;关节&#xff09; 如图&#xf…

计算机信息系统

一、概念 &#xff08;1&#xff09; 计算机信息系统&#xff08; Computer_based Information System &#xff0c;简称信息系统&#xff09;是一类以提供信息服务 为主要目的的数据密集型、人机交互的计算机应用系统。 &#xff08;2&#xff09;由计算机及其相关的和配套的…

物理服务器的信息,信息物理系统

信息物理系统(CPS,Cyber-Physical Systems)是一个综合计算、网络和物理环境的多维复杂系统&#xff0c;通过3C(Computation、Communication、Control)技术的有机融合与深度协作&#xff0c;实现大型工程系统的实时感知、动态控制和信息服务。CPS实现计算、通信与物理系统的一体…

小白聊智慧制造:一文读懂信息物理系统(CPS)

随着我国“中国制造2025”的不断深入&#xff0c;越来越多的制造业企业在探索中国的制造业升级。制造业的智能升级有美国的工业互联网和德国的工业4.0两种方式&#xff0c;根据我国制造业所处的阶段&#xff0c;大多数企业选择德国工业4.0的方案。工业4.0的方案之中&#xff0c…

软考 - 05 信息物理系统(Cyber Physical Systems, CPS)

文章目录 题目【问题1】【答案1】【问题2】【答案2】【问题3】【答案3】 题目 信息物理系统&#xff08;Cyber Physical Systems, CPS)技术己成为未来宇航装备发展的重点关键技术之一。某公司长期从事嵌入式系统的研制工作&#xff0c;随着公司业务范围不断扩展&#xff0c;公…