用户上传的 PDF 里,总有一部分是带着密码的。程序若不先问一句就直接处理,轻则抛异常中断,重则生成一份内容残缺的输出。与其等失败之后再回头排查,不如在入口处把文档查清楚:它加密了没有、需要哪种密码、手上的候选密码对不对。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接读取 PDF 文档,通过虚拟文件系统(VFS)管理输入文件,无需后端配合。PdfDocument.IsPasswordProtected() 是一个静态方法,不用打开文档、也不用提供密码,直接读文件即可判断;文档载入后,PdfDocument.Security 还会记录本次使用的密码落在 UserPassword 还是 OwnerPassword,据此可以确认密码的角色。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
判断 PDF 是否受密码保护
PdfDocument.IsPasswordProtected() 接收虚拟文件系统中的文件名,返回布尔值,判断的是文档里有没有加密字典,因此不需要打开文档,也不需要密码。它读的是虚拟文件系统里的路径,必须先执行 window.spire.FetchFileToVFS() 把文件读进去,否则会抛 File doesn't exist。
function App() {
const checkPasswordProtection = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 待检测的文件名
const plainFileName = '合同模板.pdf';
const lockedFileName = '加密合同.pdf';
// 静态方法读的是虚拟文件系统,先把文件载入
await window.spire.FetchFileToVFS(plainFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(lockedFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 逐个判断,不需要打开文档,也不需要密码
const reportLines = [];
for (const fileName of [plainFileName, lockedFileName]) {
const isProtected = pdfModule.PdfDocument.IsPasswordProtected(fileName);
reportLines.push(`${fileName}:${isProtected ? '受密码保护' : '未加密'}`);
}
// 结果写入虚拟文件系统后导出
const outputFileName = '加密检测结果.txt';
const report = reportLines.join('\r\n');
window.dotnetRuntime.Module.FS.writeFile(outputFileName, new TextEncoder().encode(report));
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>判断 PDF 是否受密码保护</h1>
<button onClick={checkPasswordProtection}>
开始检测
</button>
</div>
);
}
export default App;
导出的检测报告:合同模板未加密,加密合同受密码保护

判断文档是否需要打开密码
IsPasswordProtected() 返回 true 只说明文档带加密字典,并不等于必须输密码才能打开——只设了权限密码的文档依然可以免密阅读。要区分这两种情况,直接免密调用一次 LoadFromFile():抛异常说明缺打开密码,能正常载入则说明文档只是受限。
function App() {
const checkOpenPassword = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 待判定的文件名
const fileNames = ['合同模板.pdf', '加密合同.pdf'];
for (const fileName of fileNames) {
await window.spire.FetchFileToVFS(fileName, "", `${process.env.PUBLIC_URL}/data/`);
}
// 免密载入:抛异常即说明需要打开密码
const reportLines = [];
for (const fileName of fileNames) {
try {
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(fileName);
reportLines.push(`${fileName}:${doc.IsEncrypted ? '可直接打开,但文档仍受权限限制' : '未加密,可直接打开'}`);
doc.Close();
} catch (error) {
reportLines.push(`${fileName}:需要打开密码`);
}
}
// 结果写入虚拟文件系统后导出
const outputFileName = '打开密码判定.txt';
const report = reportLines.join('\r\n');
window.dotnetRuntime.Module.FS.writeFile(outputFileName, new TextEncoder().encode(report));
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>判断文档是否需要打开密码</h1>
<button onClick={checkOpenPassword}>
开始判定
</button>
</div>
);
}
export default App;
导出的判定结果:合同模板可直接打开,加密合同需要打开密码

校验候选密码并确认密码角色
密码对不对,由 LoadFromFile() 能否成功载入来回答,失败时一律抛 Can not open an encrypted document. The password is invalid.,不区分“没给密码”还是“密码错误”。所以先看文档是否加密——未加密的直接跳过,加密的逐个试密码,再从 PdfDocument.Security 读出这次用的是 UserPassword 还是 OwnerPassword,确认它属于打开密码还是权限密码。
function App() {
const verifyPassword = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 待校验的文件与候选密码
const fileNames = ['合同模板.pdf', '加密合同.pdf'];
const candidates = ['wrong123', 'spire123', 'owner123'];
// 静态方法与载入都读虚拟文件系统,先把文件载入
for (const fileName of fileNames) {
await window.spire.FetchFileToVFS(fileName, "", `${process.env.PUBLIC_URL}/data/`);
}
const reportLines = [];
for (const fileName of fileNames) {
// 未加密的文档无需验证密码
if (!pdfModule.PdfDocument.IsPasswordProtected(fileName)) {
reportLines.push(`${fileName}:该PDF文档没有加密,无需验证密码`);
continue;
}
// 已加密:逐个尝试候选密码,能载入即为正确密码
for (const password of candidates) {
try {
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(fileName, password);
// Security 记录的是本次使用的密码,据此判断密码角色
const role = doc.Security.UserPassword ? '打开密码' : '权限密码';
reportLines.push(`${fileName}:密码 "${password}" 是正确的(${role})`);
doc.Close();
} catch (error) {
reportLines.push(`${fileName}:密码 "${password}" 不正确`);
}
}
}
// 结果写入虚拟文件系统后导出
const outputFileName = '密码校验结果.txt';
const report = reportLines.join('\r\n');
window.dotnetRuntime.Module.FS.writeFile(outputFileName, new TextEncoder().encode(report));
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>校验候选密码并确认密码角色</h1>
<button onClick={verifyPassword}>
开始校验
</button>
</div>
);
}
export default App;
导出的校验结果:错误密码被拒绝,spire123 是打开密码,owner123 是权限密码

常见问题
IsPasswordProtected() 抛 File doesn't exist
原因:该方法读的是虚拟文件系统里的路径,不是浏览器能直接访问的地址。文件没有先载入虚拟文件系统时,Spire.PDF 找不到目标,直接抛出 File doesn't exist Arg_ParamName_Name, fileName。
解决:调用前先用 FetchFileToVFS 把文件读进虚拟文件系统,文件名与后续传入的名字保持一致:
// 先载入虚拟文件系统,再判断
await window.spire.FetchFileToVFS('加密合同.pdf', "", `${process.env.PUBLIC_URL}/data/`);
const isProtected = pdfModule.PdfDocument.IsPasswordProtected('加密合同.pdf');
文档明明需要密码,报错却说“密码无效”
原因:Spire.PDF 对“没有提供密码”和“密码填错”返回同一条提示 Can not open an encrypted document. The password is invalid.,单看报错无法区分。
解决:按三步走——先用 IsPasswordProtected() 判断文档是否带加密字典,再免密载入区分“需要打开密码”与“仅权限受限”,最后才用候选密码逐个尝试,由是否抛出异常来判定密码是否正确:
// 免密失败 → 需要打开密码;换密码再试 → 是否抛错即是否命中
try {
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(fileName, password);
// 载入成功:读 Security 确认角色
} catch (error) {
// 载入失败:该密码不可用
}
载入后读 Security.Permissions 报错
原因:权限位是多个标志位的组合值,不一定落在 PdfPermissionsFlags 枚举成员上,读取时会抛 Invalid value for spirepdfPdfPermissionsFlags。同一版本上 HasExtendedRight() 也会抛 ArgumentNullException。
解决:改用 Security.UserPassword 与 Security.OwnerPassword 判断本次密码的角色——权限密码载入时拥有全部权限,可以直接调用 Decrypt() 移除保护;打开密码载入时需另提供权限密码。需要精确控制权限时,改在加密环节用 PdfDocumentPrivilege 设置并自行记录:
const role = doc.Security.UserPassword ? '打开密码' : '权限密码';
// 权限密码载入的情况下,可直接移除保护
if (role === '权限密码') {
doc.Decrypt();
}
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









