Passwortgeschützte Dokumente laden mit dem jadice web toolkit
- Former user (Deleted)
- David Werth
- Kevin Hertfelder
- Daniela Gehle
Dieser Artikel beschreibt, wie passwortgeschützte PDF-Dokumente mit dem jadice web toolkit geladen werden können
Die vorgestellte Lösung ist im Showcase des jadice web toolkit unter http://webtoolkit.jadice.com/showcase/index.html#!EncryptedDocumentExample abrufbar. Das Passwort für das dort verwendete Dokument lautet J4d1c3
Anforderung
Ein PDF-Dokument, zu dessen Anzeige ein Passwort benötigt wird, soll im jadice web toolkit dargestellt werden. Dazu wird beim Öffnen des Dokuments ein Passwort in einem Benutzerdialog abgefragt. Anschließend wird versucht, das Dokument mit diesen Passwort zu öffnen.
Grundlegendes Prinzip
Lesen eines passwortgeschützten Dokuments mit dem DocumentDataProvider
Um ein passwortgeschütztes Dokument mit Mitteln der jadice documentplatform öffnen zu können, muss in den PDFStandardSecurityHandlerSettings
des Reader
s ein CryptoMaterialProvider
gesetzt werden. Das vom CryptoMaterialProvider
s bereitgestellte PasswordMaterial
enthält dann einen String
, mit dem versucht wird, das Dokument zu öffnen.
Das Setzen dieser Einstellung kann beispielsweise in der der read()
- beziehungsweise der recovery()
Methode des verwendeten DocumentDataProvider
erfolgen. Es ist allerdings auch möglich, in diese Methoden eine Reader
-Instanz zu übergeben, für die die PDFStandardSecurityHandlerSettings
bereits zuvor gesetzt wurden.
Das folgende Code-Beispiel enthält einen Auszug der Implementierung eines DocumentDataProvider,
bei der stets versucht wird, das DEFAULT_PASSWORD
zum Öffnen des Dokuments zu verwenden. In der Praxis wird man das Passwort eher über einen Benutzerdialog abfragen und in Source
beziehungsweise Handle
hinterlegen.
@Override public void read(Reader reader, final S source) throws JadiceException, IOException { // Abfragen der PDFStandardSecurityHandlerSettings beim Reader. PDFStandardSecurityHandlerSettings pdfStandardSecurityHandlerSettings = reader.getSettings( PDFStandardSecurityHandlerSettings.class); // Definition des CryptoMaterialProviders CryptoMaterialProvider<PasswordMaterial> cryptoMaterialProvider = new CryptoMaterialProvider<PasswordMaterial>() { @Override // Der CryptoMaterialProvider wiederum stellt einem CryptoMaterialReceiver das // PasswordMaterial zur Verfügung. public void provide(CryptoMaterialReceiver<PasswordMaterial> receiver) { // Bereistellung des PasswordMaterial mit dem fixen Passwort DEFAULT_PASSWORD receiver.receive(new PasswordMaterial(DEFAULT_PASSWORD)); } }; // setzen des oben definierten cryptoMaterialProvider in den pdfStandardSecurityHandlerSettings // des Readers pdfStandardSecurityHandlerSettings.setCryptoMaterialProvider(cryptoMaterialProvider); // eigentlicher Lesevorgang im Reader Provider<InputStream, IOException> stream = getStream(source); reader.read(stream); } @Override public void recover(Reader reader, final SH handle) throws RecoverFailedException, JadiceException { // Abfragen der PDFStandardSecurityHandlerSettings beim Reader. PDFStandardSecurityHandlerSettings pdfStandardSecurityHandlerSettings = reader.getSettings(PDFStandardSecurityHandlerSettings.class); // Definition des CryptoMaterialProviders CryptoMaterialProvider<PasswordMaterial> cryptoMaterialProvider = new CryptoMaterialProvider<PasswordMaterial>() { // Der CryptoMaterialProvider wiederum stellt einem CryptoMaterialReceiver das // PasswordMaterial zur Verfügung. @Override public void provide(CryptoMaterialReceiver<PasswordMaterial> receiver) { // Bereistellung des PasswordMaterial mit dem fixen Passwort DEFAULT_PASSWORD receiver.receive(new PasswordMaterial(DEFAULT_PASSWORD)); } }; // setzen des oben definierten cryptoMaterialProvider in den pdfStandardSecurityHandlerSettings // des Readers pdfStandardSecurityHandlerSettings.setCryptoMaterialProvider(cryptoMaterialProvider); try { Provider<InputStream, IOException> stream = getRecoveryStream(handle); reader.read(stream); } catch (IOException e) { throw new RecoverFailedException("Can't recover " + handle, e); } }
Weitere Implementierungsbeispiele eines CryptoMaterialProvider
finden sich in den weiterführenden Links am Ende dieses Artikels. In der dort verlinkten Dokumentation wird auch erklärt, wie ein CryptoMaterialProvider
mit mehreren Standard-Passwörtern implementiert werden kann.
Der Vergleich des Passworts aus dem PasswordMaterial
mit dem im Dokument gesetzten Wert erfolgt auf Basis eine byte-Array, nicht als String-Vergleich! Deswegen kann es bei Verwendung unterschiedlicher Encodings zu Fehlern kommen, auch wenn offenbar der korrekte String im PasswordMaterial
gesetzt wurde. Ein Workaround für dieses Problem ist ebenfalls am Ende dieses Artikels dargestellt.
Anfordern des Passworts beim Benutzer
Ist beim Laden eines PDF-Dokuments das benötigte Passwort serverseitig nicht bekannt wird dort zunächst eine PDFSecurityDocumentCreationException
geworfen.
Diese kann anschließend in Form eines MimicryThrowable an den Client propagiert werden. Dort kann dann durch einen Benutzerdialog das Passwort abgefragt und an den Server übertragen werden.
Ist das Passwort korrekt, wird das Dokument geladen und angezeigt. Kann das Dokument mit dem eingegebenen Passwort nicht geöffnet werden, wird erneut eine PDFSecurityDocumentCreationException
geworfen und als MimicryThrowable
an den Client gesendet.
Der serverseitige Ladevorgang kann durch den Client abgebrochen werden, beispielsweise wenn das Passwort dem Benutzer nicht bekannt ist.
Da das Passwort bei jedem Ladevorgang des Dokuments auf dem Server vorhanden sein muss, sollte sichergestellt sein dass das Passwort dort lange genug vorgehalten wird. Damit wird sichergestellt. dass das Dokument im Recovery-Fall nach der Entfernung aus dem Cache ohne Nutzerinteraktion neu geladen werden kann.
Beispielhafte Implementierung im Showcase
Eine beispielhafte Implementierung zur Anzeige passwortgeschützter PDF-Dateien wird im Showcase unter https://webtoolkit.jadice.com/showcase/index.html#!EncryptedDocumentExample vorgestellt.
Dort findet in Example.java
der Ladevorgang des passwortgeschützten Dokuments encr.pdf
statt. Dabei wird dem Reader im Aufruf von Reader#complete(...)
ein AsyncCallback
-Objekt übergeben, dessen onFailure
-Methode bei Fehlern beim Dokumentenladevorgang aufgerufen wird.
Innerhalb der onFailure
-Methode wird geprüft, ob der Fehler durch ein falsches oder fehlendes Passwort verursacht wurde. Ist dies der Fall wird ein PasswortDialog angezeigt.
Der PasswortDialog hinterlegt das Passwort bei Bestätigung der Eingabe in einem Source-Handle, das anschließend an den Reader übergeben wird.
Weitere Information zum Lesen von Dokumenten mit Source-Handles finden sich in Kapitel zwei des Tutorials "Getting Started" unter 2 - Laden eines Dokuments
- |
Passwort-Dialog aus PasswortDialog.java |
Erläuterungen zur Implementierung
In diesem Abschnitt soll anhand von Code-Auszügen die Implementierung des Showcase erläutert werden.
Example.java
Nach einem Klick auf den Button "Load password protected PDF" wird die darauf registrierte Methode onClikck()
aufgerufen.
Innerhalb dieser Methode wird der Reader konfiguriert mit dem das Dokument gelsen werden soll. Dabei wird auch der nötige AsyncCallback
registriert. Gleichzeitig wird der PasswortDialog
konfiguriert.
public void onClick(ClickEvent event) { Reader r = new Reader(); //Definition des source-handles zum Laden des Dokuments. ClassPathSource erbt dabei von der PasswordContainingSource, in der ein String als Passwort hinterlegt werden kann. ClassPathSource source = new ClassPathSource("com/levigo/jadice/web/demo/showcase/client/examples/document/encr.pdf"); // Hinzufügen des Source-Handles zum reader r.append(source); // Definition des Passwortdialogs mit der PageView auf der dieser gegebenenfalls angezeigt werden soll und des source-Handle in dem das Passwort hinterlegt werden soll PasswordDialog passwordDialog = new PasswordDialog(viewer.getPageView(), source); // Definition des AsyncCallback der gegebenenfalls die Anzeige des Passwortdialogs steuert. AsyncCallback<Document> asyncCallback = new AsyncCallback<Document>() { // Tritt kein Fehler auf weil das Passwort korrekt eingegeben wurde oder weil kein Passwort benötigt wird wird das Dokument als Inahlt der PageView gesetzt. @Override public void onSuccess(Document result) { viewer.getPageView().setDocument(result); } //Ist ein Fehler aufgetreten weil das Passwort nicht vorhanden oder falsch war soll der Passwortdialog angezeigt werden. @Override public void onFailure(Throwable caught) { if (passwordDialog != null && // caught instanceof MimicryThrowable && // ((MimicryThrowable) caught).getClassName().equals(PDFSecurityDocumentCreationException.class.getName())) { passwordDialog.setAsyncCallback(this); passwordDialog.show(); } } }; // Abschließen des Readers mit Registrierung des Callbacks r.complete(asyncCallback); }
PasswordDialog.java
In PasswordDialog.java
wird neben den funktionalen Elementen des angezeigten Dialogs auch wichtige Funktionalität implementiert. Diese soll in diesem Abschnitt weiter erläutert werden.
// Die execute()-Methode wird nach Aktivierung des confirm-Buttons aufgerufen. public void execute(String password) { //Setzen des vom Benutzer eingetragenen Passworts im source-Handle source.setPassword(password); //Anschließend wird ein neuer Reader erzeugt, an den source-Handle mit Passwort angehängt wird. Reader reader = new Reader(); reader.append(source); // Abschließen des Readers mit Registrierung desselben Callbacks der bereits zur Anzeige des PasswordDialog geführt hat. reader.complete(callback); } // Um den Ladevorgang abzubrechen wird die onFailure-Methode des AsnycCallback mit einem Throwable aufgerufen das kein Mimicry-Throwable ist. Würde hier ein Mimicry-Throwable übergeben würde der PasswordDialog erneut angezeigt. public void abort() { callback.onFailure(new Throwable("PasswordDialog aborted. Missing password for encrypted document.")); }
Weiterführende Links
- Grundlagen zur Verschlüsselung und zum Schutz von PDF-Dokumenten werden in der Dokumentation der jadice documentplatform im Kapitel PDF: Verschlüsselung behandelt.
- Konkrete Beispiele zur Verwendung der PDF-Security API in der jadice documentplatform sind in der Entwicklerdokumentation im Kapitel Beispiel zur Verwendung der PDF Security API erklärt.
- Im Knowledge-Base Artikel Verschlüsselte PDFs sind die Grundlagen des Schutzes von PDF-Dateien mit Passwörtern erklärt.
Bekannte Probleme
Dokumente werden trotz korrektem Passwort nicht geladen
Abhängig davon, welches Encoding für das Passwort eines Dokuments verwendet wurde, kann es vorkommen, dass ein Dokument trotz korrekt eingegebenem Passwort nicht angezeigt werden kann. Dies liegt daran, dass das als String übergebene Passwort im PasswordMaterial
der jadice documentplatform als byte-Array weiterverwendet werden muss.
Ein Workaround zu diesem Problem ist, in der read-Methode des DocumentDataProvider
eine angepasste Implementierung des PasswordMaterial
zu verwenden, bei der das zu verwendende Encoding definiert werden kann. Ist das Encoding nicht vorab bekannt, können verschiedene Encodings nacheinander ausprobiert werden.
Eine Implementierung einer solchen alternativen PasswordMaterial
-Implementierung könnte folgendermaßen aussehen:
import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; /** * A custom implementation of {@link PasswordMaterial} which uses a custom charset to create the * password bytes in {@link #getPasswordBytes()}. */ public class CustomCharsetPasswordMaterial extends PasswordMaterial { private final Charset charset; /** * constructs a new instance of {@link CustomCharsetPasswordMaterial} which will provide the given * password encoded with the given charset. * * @param password password to be provided * @param charset the charset to use */ public CustomCharsetPasswordMaterial(String password, Charset charset) { super(password); this.charset = charset; } /** * @return a byte-array representation of the password held by this instance of * {@link CustomCharsetPasswordMaterial} */ @Override public byte[] getPasswordBytes() { return getPassword().getBytes(charset); } }