了解如何为 Confluence 与 ONLYOFFICE 创建集成插件以在 Confluence 中添加 DOCX、XLSX 与 PPTX 编辑功能。
在线文档编辑功能可作为数字化协同工作区的宝贵补充,为此您还可使用开源工具。在本文中,我们将向您展示如何通过集成 ONLYOFFICE 文档来在 Confluence(Atlassian 推出的网页端企业 Wiki)中引入文档编辑功能。
我们将创建一个集成应用程序(插件)来发挥沟通 Confluence 与 ONLYOFFICE 的桥梁作用。在其帮助下,用户将可在 Confluence 中编辑 DOCX、XLSX、PPTX 和其他办公文件。
安装 ONLYOFFICE 文档
在此过程中,我们将需要可正常工作的 ONLYOFFICE 文档实例。其已被打包为了文档服务器,所以我们将在本文中使用此说法。
您可使用 Docker 来进行安装,或选择其他安装选项:
docker run -i -t -d -p 8080:80 onlyoffice/documentserver
本行代码将安装免费社区版。其中包含文档、电子表格与演示文稿编辑器,此外还有集成示例(一个简单的文档管理系统),其中演示了集成的各种功能,允许您在将编辑器集成至应用之前对其进行测试。
集成测试示例使用 Node.js 进行构建。您可查看其他语言的选项,包括 Java 测试示例。
现在,我们还是回到创建将编辑器连接至 Confluence 的插件上来。
使用 Atlassian SDK
在进行开发工作之前,我们需要安装 Atlassian SDK。基本上来说,这一 SDK 就是 Maven 的封装器。您可借助它来创建插件主体,然后在运行中的 Atlassian 应用程序中对其进行测试。
如需进行安装,可参考 Windows、Linux 或 Mac 的官方安装指南。
之后我们将为 Confluence 创建一个插件主体。打开命令行并运行 atlas-create-confluence-plugin。在创建简单插件前,这一工具将要求您提供一些细节。
您也可以在 IDE 中打开插件。部分 IDE 可能需要进行额外配置。
虽然在对 VSCode 进行配置方面没有太多说明,但相关工作非常简单:您只需将其指向 SDK 自带的 Maven 即可。在 .vscode 文件夹中创建 settings.json 并填入以下内容:
{
"maven.executable.path": "C:\\Applications\\Atlassian\\atlassian-plugin-sdk-8.0.16\\apache-maven-3.5.4\\bin\\mvn\",
"java.configuration.maven.userSettings": "C:\\Applications\\Atlassian\\atlassian-plugin-sdk-8.0.16\\apache-maven-3.5.4\\conf\\settings.xml"
}
还要注意的是,具体路径可能会因为 SDK 的安装路径不同而有所区别。
使用 UI
借助 Atlassian 应用的 UI,您可实现很多功能,但是现在我们只需创建一个编辑按钮和用于加载编辑器的页面即可。
添加按钮非常简单。实际上,您甚至都不需要编写代码(除非按钮背后还有额外的逻辑)。基本上而言,Atlassian 应用会使用一个 XML 文件来声明插件的内容。文件位于:src\main\resources\atlassian-plugin.xml。此外还有助于让我们了解应用会对哪些 Java 类进行实例化。
(小提醒:我们将在代码中排除所有 import 行以节省空间)
如需添加按钮,我们只需添加这些代码即可:
<web-item key="onlyoffice-doceditor" name="Link for the attachment editing" section="system.attachment" weight="9">
condition class="onlyoffice.IsOfficeFileAttachment">
<param name="forEdit">;true</param>;
</condition>
<description>The link and the text for it to open the document which is available for editing.</description>
<label key="onlyoffice.connector.editlink"/>
<link><![CDATA[/plugins/servlet/onlyoffice/doceditor?attachmentId=$attachment.id]]></link>
<styleClass>onlyoffice-doceditor</styleClass>
</web-item>
下面就来看看上面几行代码中的参数。为此我们也准备了一份详尽的官方文档,记得去看看。
大部分参数都无需过多解释。section 属性用于声明我们希望建立链接的位置。<condition> 元素用于声明对展示链接的附件进行过滤的 Java 类。
基本上而言,这里的条件将用于检查用户的访问权限、最大文件大小以及文件扩展名。如果您对于其工作原理比较感兴趣,可在这里找到相关代码。
当然,我们需要添加的不仅仅是编辑按钮。此外还有用于查看文档和将其转换为不同格式的按钮,但主要的流程还是相同的。
接着我们来看看 <link> 元素。这里使用附件 id 参数将其导向 Servlet(链接如下:confluence/servlet/attachment_Id)。Servlet 将对用户请求进行处理并返回需要在编辑器中进行加载的页面。
Servlet 本质上是继承自 javax.servlet.http.HttpServlet的类,但需对其 doGet 方法进行重写。这里我们还是保持简洁,暂时不去深究实现细节。
public class OnlyOfficeEditorServlet extends HttpServlet {
private static final Logger log = LogManager.getLogger("onlyoffice.OnlyOfficeEditorServlet");
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
return VelocityUtils.getRenderedTemplate("templates/editor.vm", defaults);
}
}
我们要做的唯一一件事是渲染一个 Velocity 模板。所以:src\main\resources\templates\editor.vm。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN” "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml\>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ANSI" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width" />
<title>${docTitle} - ONLYOFFICE</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<style type="text/css">
html { height: 100%; width: 100%; }
body {
background: #fff; color: #333; font-family: Arial, Tahoma, sans-serif;
font-size: 12px; font-weight: normal; text-decoration: none;
height: 100%; margin: 0; overflow-y: hidden; padding: 0;
}
.form { height: 100%; }
div { margin: 0; padding: 0; }
</style>
</head>
<body>
<div class="form">
<div id="iframeEditor"></div>
</div>
</body>
</html>
在模板中将有一个 JavaScript 脚本用于在编辑器中加载页面。您可能会注意到这里有多个 ${variables}
,我们会在稍后提供相关信息。为了让 Servlet 能够正常工作,我们需要让 Atlassian 知晓其存在。所以,这里我们需要对于 atlassian-plugin.xml. 进行一些修改。
<servlet key="OnlyOfficeDocEditor" class="onlyoffice.OnlyOfficeEditorServlet" name="Document Editor">
<description>The fully functional editor for most known formats of text documents, spreadsheets and presentations used to open these types of documents for editing or preview.</description>
<url-pattern>/onlyoffice/doceditor</url-pattern>
</servlet>
Servlet 和插件配置
此时,我们马上就能去打开文档进行查看了。现在我们还需要为页面模板提供变量。其中一个将包含指向文档服务器的 URL,这里最好不要直接写死在代码中。幸运的是,我们可以借助一个很棒的接口来对插件设置项与 UI 进行操作
下面我们就来创建另一个配置 Servlet(别忘了将其添加至 atlassian-plugin.xml 中)。
public class OnlyOfficeConfServlet extends HttpServlet {
@ComponentImport
private final UserManager userManager;
@ComponentImport
private final PluginSettingsFactory pluginSettingsFactory;
@Inject
public OnlyOfficeConfServlet(UserManager userManager, PluginSettingsFactory pluginSettingsFactory) {
this.userManager = userManager;
this.pluginSettingsFactory = pluginSettingsFactory;
}
private static final Logger log = LogManager.getLogger("onlyoffice.OnlyOfficeConfServlet");
private static final long serialVersionUID = 1L;
private String AppendSlash(String str) {
if (str == null || str.isEmpty() || str.endsWith("/"))
return str;
return str + "/";
}
private String getBody(InputStream stream) {
Scanner scanner = null;
Scanner scannerUseDelimiter = null;
try {
scanner = new Scanner(stream);
scannerUseDelimiter = scanner.useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
} finally {
scannerUseDelimiter.close();
scanner.close();
}
}
}
这里我们有两种实用方法,代码本身已足够简单明了。我们还需要另外两个方法。
第一个是 doGet。用于获取当前设置并将其提供给模板。
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = userManager.getRemoteUsername(request);
if (username == null || !userManager.isSystemAdmin(username)) {
SettingsManager settingsManager = (SettingsManager) ContainerManager.getComponent("settingsManager");
String baseUrl = settingsManager.getGlobalSettings().getBaseUrl();
response.sendRedirect(baseUrl);
return;
}
PluginSettings pluginSettings = pluginSettingsFactory.createGlobalSettings();
String apiUrl = (String) pluginSettings.get("onlyoffice.apiUrl");
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
Map<String, Object> contextMap = MacroUtils.defaultVelocityContext();
contextMap.put("docserviceApiUrl", apiUrl);
writer.write(getTemplate(contextMap));
}
private String getTemplate(Map<String, Object> map) throws UnsupportedEncodingException {
return VelocityUtils.getRenderedTemplate("templates/configure.vm", map);
}
第二个是 doPost。该方法将接收 JSON 对象,对其进行解析并覆盖当前设置。
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = userManager.getRemoteUsername(request);
if (username == null || !userManager.isSystemAdmin(username)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
String body = getBody(request.getInputStream());
if (body.isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
String apiUrl;
try {
JSONObject jsonObj = new JSONObject(body);
apiUrl = AppendSlash(jsonObj.getString("apiUrl"));
} catch (Exception ex) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
String error = ex.toString() + "\n" + sw.toString();
log.error(error);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("{\"success\": false, \"message\": \"jsonparse\"}");
return;
}
PluginSettings pluginSettings = pluginSettingsFactory.createGlobalSettings();
pluginSettings.put("onlyoffice.apiUrl", apiUrl);
response.getWriter().write("{\"success\": true}");
}
在这两种方法中,我们还会检查用户是否有权访问这些设置。实际上,我们还会检查与文档服务器之间的连接,以便对潜在问题进行识别。完整代码可在此处查看。
我们还能在 Atlassian 插件管理页面中添加一个配置按钮。只需将此行代码添加至 atlassian-plugin.xml 中的 <plugin-info> 元素内即可。
<param name="configure.url">/plugins/servlet/onlyoffice/configure</param>
对了,别忘记 Velocity 模板。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>ONLYOFFICE</title>
<meta name="decorator" content="atl.admin" />
</head>
<body>
<div id="onlyofficeMsg"></div>
<form id="onlyofficeConf" class="aui top-label">
<h3>$i18n.getText('onlyoffice.configuration.doc-section')</h3>
<div class="field-group">
<label for="apiUrlField">$i18n.getText('onlyoffice.configuration.doc-url')</label>
<input type="text" id="apiUrlField" value="${docserviceApiUrl}" name="apiUrlField" class="text onlyoffice-tooltip" title="$i18n.getText('onlyoffice.configuration.doc-url-tooltip')">
</div>
<div class="field-group">
<input id="onlyofficeSubmitBtn" type="submit" value="$i18n.getText('onlyoffice.configuration.save')" class="button">
</div>
</form>
</body>
</html>
下面让我们返回编辑器 Servlet 并对其进行修改。
public class OnlyOfficeEditorServlet extends HttpServlet {
@ComponentImport
private final LocaleManager localeManager;
private final UrlManager urlManager;
@Inject
public OnlyOfficeEditorServlet(LocaleManager localeManager, UrlManager urlManager) {
this.urlManager = urlManager;
this.localeManager = localeManager;
}
private static final Logger log = LogManager.getLogger("onlyoffice.OnlyOfficeEditorServlet");
private static final long serialVersionUID = 1L;
private Properties properties;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (!AuthContext.checkUserAuthorisation(request, response)) {
return;
}
String apiUrl = urlManager.getPublicDocEditorUrl();
if (apiUrl == null || apiUrl.isEmpty()) {
apiUrl = "";
}
ConfigurationManager configurationManager = new ConfigurationManager();
properties = configurationManager.GetProperties();
String fileUrl = "";
String key = "";
String fileName = "";
String errorMessage = "";
ConfluenceUser user = null;
String attachmentIdString = request.getParameter("attachmentId");
Long attachmentId;
try {
attachmentId = Long.parseLong(attachmentIdString);
log.info("attachmentId " + attachmentId);
user = AuthenticatedUserThreadLocal.get();
log.info("user " + user);
if (AttachmentUtil.checkAccess(attachmentId, user, false)) {
key = DocumentManager.getKeyOfFile(attachmentId);
fileName = AttachmentUtil.getFileName(attachmentId);
fileUrl = urlManager.GetFileUri(attachmentId);
} else {
log.error("access deny");
errorMessage = "You don not have enough permission to view the file";
}
} catch (Exception ex) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
String error = ex.toString() + "\n" + sw.toString();
log.error(error);
errorMessage = ex.toString();
}
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write(getTemplate(apiUrl, fileUrl, key, fileName, user, errorMessage));
}
private String getTemplate(String apiUrl, String fileUrl, String key, String fileName,
ConfluenceUser user, String errorMessage) throws UnsupportedEncodingException {
Map<String, Object> defaults = MacroUtils.defaultVelocityContext();
Map<String, String> config = new HashMap<String, String>();
String docTitle = fileName.trim();
String docExt = docTitle.substring(docTitle.lastIndexOf(".") + 1).trim().toLowerCase();
config.put("docserviceApiUrl", apiUrl + properties.getProperty("files.docservice.url.api"));
config.put("errorMessage\", errorMessage);
config.put("docTitle", docTitle);
JSONObject responseJson = new JSONObject();
JSONObject documentObject = new JSONObject();
JSONObject editorConfigObject = new JSONObject();
JSONObject userObject = new JSONObject();
JSONObject permObject = new JSONObject();
try {
responseJson.put("type", "desktop");
responseJson.put("width", "100%");
responseJson.put("height", "100%");
responseJson.put("documentType", getDocType(docExt));
responseJson.put("document", documentObject);
documentObject.put("title", docTitle);
documentObject.put("url", fileUrl);
documentObject.put("fileType", docExt);
documentObject.put("key", key);
documentObject.put("permissions", permObject);
permObject.put("edit", false);
responseJson.put("editorConfig", editorConfigObject);
editorConfigObject.put("lang", localeManager.getLocale(user).toLanguageTag());
editorConfigObject.put("mode", "edit");
if (user != null) {
editorConfigObject.put("user", userObject);
userObject.put("id", user.getName());
userObject.put("name", user.getFullName());
}
// AsHtml at the end disables automatic html encoding
config.put("jsonAsHtml", responseJson.toString());
} catch (Exception ex) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
String error = ex.toString() + "\n" + sw.toString();
log.error(error);
}
defaults.putAll(config);
return VelocityUtils.getRenderedTemplate("templates/editor.vm", defaults);
}
private String getDocType(String ext) {
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;
}
}
这将构建一个在页面上打开编辑器时所需的 JSON 对象。请注意,我们会将 JSON 对象放入结尾是 AsHtml 的变量中。其会禁用自动 HTML 编码,所以我们可将其放入模板的 <script> 标记内,如下:
var json = '${jsonAsHtml}';
这样就能正常工作了。
我们还使用了三个工具类:AttachmentUtil、UrlManager 以及 DocumentManager。这些类有点超出本文所涵盖的范围(除了 AttachmentUtil 与 DocumentManager 中的一部分内容,但我们稍后也会提到),所以如果您对此感兴趣的话,可自行进行探索。
最后还有一件事需要我们去处理:将文档提供给文档服务器。
这里我们再次创建一个 Servlet,其中唯一的方法就是提供文件。
public class OnlyOfficeSaveFileServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger log = LogManager.getLogger("onlyoffice.OnlyOfficeSaveFileServlet");
@ComponentImport
private final PluginSettingsFactory pluginSettingsFactory;
private final PluginSettings settings;
@Inject
public OnlyOfficeSaveFileServlet(PluginSettingsFactory pluginSettingsFactory, JwtManager jwtManager) {
this.pluginSettingsFactory = pluginSettingsFactory;
settings = pluginSettingsFactory.createGlobalSettings();
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String vkey = request.getParameter("vkey");
log.info("vkey = " + vkey);
String attachmentIdString = DocumentManager.ReadHash(vkey);
Long attachmentId = Long.parseLong(attachmentIdString);
log.info("attachmentId " + attachmentId);
String contentType = AttachmentUtil.getMediaType(attachmentId);
response.setContentType(contentType);
InputStream inputStream = AttachmentUtil.getAttachmentData(attachmentId);
response.setContentLength(inputStream.available());
byte[] buffer = new byte[10240];
OutputStream output = response.getOutputStream();
for (int length = 0; (length = inputStream.read(buffer)) > 0;) {
output.write(buffer, 0, length);
}
}
}
运行与测试
此时,一切准备工作都已就绪,我们可以对附件进行打开并查看。
运行 atlas-run 命令以构建插件,然后运行 Confluence。
或者,您也可以运行 atlas-package 来仅构建一个 .jar。
无论选择哪种方式,您都应该前往“设置 -> 管理应用”中上传插件并点击配置。指定文档服务器 URL 并测试插件。
编辑与共同编辑
如需对文档进行编辑,我们就还需要进行一些更改。首先来看看其工作原理。
这里有个名为 callbackUrl 的参数。其应该指向一个从文档服务器接收 JSON 数据的 Servlet,用于描述文档编辑的状态。在用户关闭文档时,其会发出一则消息告知编辑完成,还会发送更新后文档的 URL,便于我们进行下载。
您可能也会想了解一下共同编辑的原理。只要 key 参数相同,用户就可以对同一个文档进行编辑。在为文件生成 key 时有两件重要的事需要考虑。
1. 每个文档的 key 都不应相同。
2. 文档中出现变更时 key 也应当变更。
通常而言,ID + 修改日期的组合能够胜任这一工作。但也有其不适用的情况。如果您打算实现强制保存功能,那么就需要生成一个在编辑全程保持相同的 key。
key 的生成方法位于 AttachmentUtil 与 DocumentManager 中。
让我们将下面的代码添加至编辑器 Servlet 中,就放在 fileUrl = urlManager.GetFileUri(attachmentId); 代码行后方即可:
if (AttachmentUtil.checkAccess(attachmentId, user, true)) {
callbackUrl = urlManager.getCallbackUrl(attachmentId);
}
将其传递给 getTemplate 函数:
writer.write(getTemplate(apiUrl, callbackUrl, fileUrl, key, fileName, user, errorMessage));
然后在此处使用:
permObject.put("edit", callbackUrl != null && !callbackUrl.isEmpty());
...
editorConfigObject.put("callbackUrl", callbackUrl);
接着我们需要创建一个回调处理器。我们来修改一下用于提供文件的 OnlyOfficeSaveFileServlet。这一过程非常简单。我们将对传入的 JSON 对象进行解析,查看其中的 status 并在有需要时保存文档。相关文档可在此处查看。
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain; charset=utf-8");
String vkey = request.getParameter("vkey");
log.info("vkey = " + vkey);
String attachmentIdString = DocumentManager.ReadHash(vkey);
String error = "";
try {
processData(attachmentIdString, request);
} catch (Exception e) {
error = e.getMessage();
}
PrintWriter writer = response.getWriter();
if (error.isEmpty()) {
writer.write("{\"error\":0}");
} else {
response.setStatus(500);
writer.write("{\"error\":1,\"message\":\"" + error + "\"}");
}
log.info("error = " + error);
}
private void processData(String attachmentIdString, HttpServletRequest request) throws Exception {
log.info("attachmentId = " + attachmentIdString);
InputStream requestStream = request.getInputStream();
if (attachmentIdString.isEmpty()) {
throw new IllegalArgumentException("attachmentId is empty");
}
HttpURLConnection connection = null;
try {
Long attachmentId = Long.parseLong(attachmentIdString);
String body = getBody(requestStream);
log.info("body = " + body);
if (body.isEmpty()) {
throw new IllegalArgumentException("requestBody is empty");
}
JSONObject jsonObj = new JSONObject(body);
long status = jsonObj.getLong("status");
log.info("status = " + status);
// MustSave, Corrupted
if (status == 2 || status == 3) {
ConfluenceUser user = null;
JSONArray users = jsonObj.getJSONArray("users");
if (users.length() > 0) {
String userName = users.getString(0);
UserAccessor userAccessor = (UserAccessor) ContainerManager.getComponent("userAccessor");
user = userAccessor.getUserByName(userName);
log.info("user = " + user);
}
if (user == null || !AttachmentUtil.checkAccess(attachmentId, user, true)) {
throw new SecurityException("Try save without access: " + user);
}
String downloadUrl = jsonObj.getString("url");
log.info("downloadUri = " + downloadUrl);
URL url = new URL(downloadUrl);
connection = (HttpURLConnection) url.openConnection();
int size = connection.getContentLength();
log.info("size = " + size);
InputStream stream = connection.getInputStream();
AttachmentUtil.saveAttachment(attachmentId, stream, size, user);
}
} catch (Exception ex) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
String error = ex.toString() + "\n" + sw.toString();
log.error(error);
throw ex;
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
private String getBody(InputStream stream) {
Scanner scanner = null;
Scanner scannerUseDelimiter = null;
try {
scanner = new Scanner(stream);
scannerUseDelimiter = scanner.useDelimiter("\\A");
return scanner.hasNext() ? scanner.next() : "";
} finally {
scannerUseDelimiter.close();
scanner.close();
}
}
好了,这就 OK 啦。接下来就是对项目进行重新编译与测试了。
安全性
您可能想知道,我们如何确保通过 POST 方式传递至 Servlet 的 JSON 确实来自文档服务器。答案很简单:文档服务器使用 JWT 来实现这一功能。JWT 是很热门的话题,也是值得单独用一些篇幅来进行介绍的话题,这里我们就不展开了。
我们所使用的是 JwtManager 类。基本上而言,JWT 是基于密钥的哈希加密 JSON。
首先我们需要为密钥添加一个新的设置。这应该不难。您可在这里找到代码:configure.vm 模板、配置 Servlet。
然后我们会向编辑器 Servlet 构造函数中添加 JwtManager,并在 config.put("jsonAsHtml", responseJson.toString()); 代码行之前对其进行使用:
if (jwtManager.jwtEnabled()) {
responseJson.put("token", jwtManager.createToken(responseJson));
}
现在我们就取得了文档服务器的信任!
为了确保文档服务器也能得到我们的信任,我们将在处理回调时使用 JWT。
文档服务器传递 JWT 的方式有两种:通过 HTTP 标头传递,或包含在 JSON 中传递,具体取决于配置情况。这里我们将同时对两者进行了解。
下面来修改一下回调 Servlet,在其构造函数中添加 JwtManager,并在 JSONObject jsonObj = new JSONObject(body); 代码行后使用:
if (jwtManager.jwtEnabled()) {
String token = jsonObj.optString("token");
Boolean inBody = true;
if (token == null || token == "") {
String jwth = (String) settings.get("onlyoffice.jwtHeader");
String header = (String) request.getHeader(jwth == null || jwth.isEmpty() ? "Authorization" : jwth);
token = (header != null && header.startsWith("Bearer ")) ? header.substring(7) : header;
inBody = false;
}
if (token == null || token == "") {
throw new SecurityException("Try save without JWT");
}
if (!jwtManager.verify(token)) {
throw new SecurityException("Try save with wrong JWT");
}
JSONObject bodyFromToken = new JSONObject(
new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8"));
if (inBody) {
jsonObj = bodyFromToken;
} else {
jsonObj = bodyFromToken.getJSONObject("payload");
}
}
好了,收工!现在我们就能在 JWT 的保护下查看和编辑文档了,其将不会受到未授权访问的侵扰,此外还有便捷的配置项可供使用。