Tabellen vor unerlaubtem Zugriff schützen

In Ausgabe 3/2019 haben wir einige Elemente vorgestellt, die den Zugriff auf die Daten einer Datenbank einschränken sollen – zum Beispiel eine einfache Benutzerverwaltung und die Möglichkeit, den Zugriff auf Tabellen per Datenmakro einzuschränken. Im vorliegenden Beitrag wollen wir zeigen, wie Sie diese Techniken abrunden und den direkten Zugriff des Benutzers auf die Tabellen der Datenbank endgültig verhindern. Dazu exportieren wir diese in eine Backend-Datenbank, auf die wir diesmal nicht wie gewohnt per Verknüpfung zugreifen – sondern ausschließlich per VBA. Das hat den Vorteil, dass wir das Backend mit einem Kennwort versehen können, das nur im Code des Frontends zum Einsatz kommt – und diesen können wir durch Umwandlung in eine .mde- beziehungsweise .accde-Datenbank vor den Augen des Benutzers verbergen.

Voraussetzungen

Die Beispieldatenbank dieses Beitrags haben wir in einigen anderen Beiträgen vorbereitet. Der Beitrag Benutzerverwaltung mit verschlüsselten Kennwörtern (www.access-im-unternehmen.de/1190) zeigt, wie die Benutzerverwaltung programmiert wird.

Unter dem Titel Zugriffsrechte mit Datenmakros (www.access-im-unternehmen.de/1193) zeigen wir, wie Sie Tabellen so schützen, dass Sie nur noch nach der Anmeldung unter einem bestimmten Benutzerkonto auf die enthaltenen Daten zugreifen können.

Die Beispieldatenbank enthält also einen Anmeldedialog namens frmAnmeldung, mit dem der Benutzer die Zugangsdaten eingeben kann. Hat er keine Zugangsdaten eingegeben, ist durch die Definition entsprechender Datenmakros kein schreibender Zugriff auf die Tabellen der Datenbank möglich. Den lesenden Zugriff schränken wir noch durch die Prüfung der Berechtigungen beim Öffnen der jeweiligen Formulare ein.

Geplante Änderungen

Die erste Änderung, die wir durchführen, ist das Exportieren der Tabellen der Anwendung in eine Backend-Datenbank. Danach schützen wir diese Datenbank durch ein Datenbankkennwort. Schließlich müssen wir überall, wo wir in der Frontend-Datenbank auf die Daten der Tabellen oder Abfrage zugegriffen haben, entsprechenden VBA-Code für den Zugriff auf die Daten unter Berücksichtigung des Datenbankkennworts des Backends hinterlegen.

Natürlich ist das je nach der Komplexität der Anwendung eine mehr oder weniger große Aufgabe, aber wenn Sie eine Access-Anwendung ohne Einsatz des SQL Servers oder eines ähnlichen Systems unter maximaler Datensicherheit erstellen wollen, müssen Sie dies in Kauf nehmen. Schließlich wandeln Sie die Frontend-Datenbank in eine .mde– beziehungsweise .accde-Datenbank um, deren Code der Benutzer nicht mehr einsehen kann. Dieser Schritt ist wichtig, da wir das Kennwort für den Zugriff auf die Tabellen des Backends im Code hinterlegen.

Exportieren der Tabellen in eine Backend-Datenbank

Das Aufteilen der Datenbank erledigen Sie am einfachsten durch den Aufruf des Ribbon-Befehls Datenbanktools|Daten verschieben|Access-Datenbank (siehe Bild 1). Nach dem Aufruf wählen Sie im Dialog Assistent zur Datenbankaufteilung den Befehl Datenbank aufteilen. Anschließend brauchen Sie nur noch den Pfad der zu erstellenden Backend-Datenbank anzugeben. Wir nennen das Backend Daten.accdb und speichern es im gleichen Verzeichnis wie die Frontend-Datenbank. Danach zeigt die Frontend-Datenbank nicht mehr die Tabellen selbst im Bereich Tabellen des Navigationsbereich an, sondern nur noch die Verknüpfungen auf die Tabellen (siehe Bild 2). Diese befinden sich nun in der neu erstellten Backend-Datenbank.

Aufruf des Befehls zum Aufteilen einer Datenbank

Bild 1: Aufruf des Befehls zum Aufteilen einer Datenbank

Verknüpfungen zu den Tabellen des Backends

Bild 2: Verknüpfungen zu den Tabellen des Backends

Solange sich diese Verknüpfungen in der Frontend-Datenbank befinden, kann der Benutzer der Datenbank zumindest lesend auf die Daten der Tabellen zugreifen, da er diese über einen Doppelklick auf die Verknüpfungen öffnen kann. Um das zu verhindern, greifen wir zu einer harten Maßnahme: Wir löschen die Verknüpfungen schlicht und einfach. Damit funktionieren die Abfragen und Formulare, die an die Tabellen gebunden sind, nun nicht mehr, da die Datenquelle nicht mehr verfügbar ist. Und auch die Formulare wie etwa frmBerechtigungenVerwalten, die per VBA auf die Tabellen zugreifen, um die Daten in einer HTML-Tabelle anzuzeigen, versagen ihren Dienst.

Schützen der Backend-Datenbank durch ein Kennwort

Bevor wir die notwendigen Änderungen am Frontend vornehmen, um wieder auf die Daten der Tabellen zuzugreifen, legen wir ein Datenbankkennwort für die Backend-Datenbank fest. Dazu öffnen Sie diese direkt in einer weiteren Access-Instanz. Dazu genügt ein Doppelklick auf die Datei Daten.accdb.

Anschließend öffnen Sie mit einem Klick auf den Ribbon-Reiter Datei den Backstage-Bereich. Unter Informationen finden Sie hier die Schaltfläche Mit Kennwort verschlüsseln (siehe Bild 3). Wenn Sie die Datenbank per Doppelklick geöffnet haben, ist diese noch nicht im Exklusiv-Modus geöffnet. Dies können Sie erledigen, indem Sie die Datenbank mit dem Ribbon-Befehl Datei|Schließen schließen. Danach betätigen Sie den Ribbon-Befehl Datei|Öffnen. Hier wählen Sie den Befehl Durchsuchen aus. Im nun erscheinenden Öffnen-Dialog wählen Sie die gewünschte Datei aus, in diesem Fall Daten.accdb, und wählen den Untereintrag Exklusiv öffnen der Schaltfläche Öffnen aus.

Zuweisen eines Datenbankkennworts zur Backend-Datenbank

Bild 3: Zuweisen eines Datenbankkennworts zur Backend-Datenbank

Danach können Sie erneut den Ribbon-Reiter Datei anklicken und im Bereich Information die Schaltfläche Mit Kennwort verschlüsseln betätigen. Nun erscheint der Dialog Datenbankkennwort festlegen, in dem Sie das Kennwort zwei Mal eingeben und dieses dann durch einen Klick auf die Schaltfläche OK bestätigen (siehe Bild 4). Zu Beispielzwecken haben wir hier das Kennwort kennwort eingetragen.

Festlegen des Datenbankkennworts

Bild 4: Festlegen des Datenbankkennworts

Wenn Sie die Backend-Datenbank nun schließen und erneut öffnen, erscheint der Dialog Kennwort erforderlich und verlangt nach der Eingabe des Kennworts.

Ersetzen der Bindung durch Zuweisen von Recordsets

Damit haben wir den Zugriff auf die Daten vom Frontend aus noch längst nicht wiederhergestellt – im Gegenteil, durch Vergabe des Kennworts haben wir diesen sogar noch erschwert. Doch nun werden wir Schritt für Schritt den Zugriff auf die Tabellen des Backends wieder hinzufügen.

Wenn wir die Frontend-Datenbank öffnen, sollte nach dem Bestätigen des Intro-Formulars der Dialog frmAnmelden erscheinen. Stattdessen erhalten wir die Fehlermeldung aus Bild 5. Als fehlerhafte Zeile wird die folgende Zeile markiert:

Fehlermeldung beim Versuch, auf die Tabelle tblBenutzer zuzugreifen

Bild 5: Fehlermeldung beim Versuch, auf die Tabelle tblBenutzer zuzugreifen

Set rst = db.OpenRecordset("SELECT Benutzername FROM tblBenutzer", dbOpenDynaset)

Hier benötigen wir nur eine kleine Anpassung, die wie folgt aussieht:

Set rst = db.OpenRecordset("SELECT Benutzername FROM [;PWD=kennwort;DATABASE=" & CurrentProject.Path _
     & "Daten.accdb].tblBenutzer", dbOpenDynaset)

Wir fügen also lediglich vor dem Namen der Tabelle einen Ausdruck in eckigen Klammern hinzu, der das Kennwort sowie den Pfad zur Backend-Datenbank enthält. Wenn wir das Formular frmAnmeldung nun öffnen, erscheint dieses ohne Fehler, denn es ist auch nicht an eine der Tabellen gebunden.

Während der Aktualisierung des Benutzernamens bei der Eingabe der Anmeldedaten wird jedoch in der Prozedur txtBenutzername_Change ein weiteres Recordset geöffnet, das die Tabelle tblBenutzer als Datenquelle verwendet. Hier passen wir die Anweisung wie folgt an:

Set rstBenutzer = db.OpenRecordset("SELECT * FROM [;PWD=kennwort;DATABASE=" & CurrentProject.Path _
     & "Daten.accdb].tblBenutzer WHERE Benutzername = ''" & strBenutzername & "''", dbOpenDynaset)

An dieser Stelle wird klar: Wir benötigen den Ausdruck [;PWD=kennwort;DATABASE=“ & CurrentProject.Path & „Daten.accdb] an mehr als einer Stelle. Wir können diesen Ausdruck an jeder Stelle einfügen, an der es erforderlich ist, allerdings wartet dann eine Menge Arbeit auf uns, wenn sich der Pfad der Backend-Datenbank ändert – oder auch das Kennwort dieser Datenbank. Also erstellen wir dazu zunächst eine einfache Funktion, welche uns die gewünschte Zeichenkette zusammenstellt:

Public Function BackendPathPassword() As String
     BackendPathPassword = "[;PWD=kennwort;DATABASE=" _
         & CurrentProject.Path & "Daten.accdb]."
End Function

Der Zugriff auf diese Funktion sieht in der Anweisung aus dem ersten Beispiel wie folgt aus:

Set rst = db.OpenRecordset("SELECT Benutzername FROM " & BackendPathPassword & "tblBenutzer", dbOpenDynaset)

Und für das zweite Beispiel sieht der Aufruf so aus:

Set rstBenutzer = db.OpenRecordset("SELECT * FROM " _
              & BackendPathPassword & "tblBenutzer WHERE  Benutzername = ''" & strBenutzername & "''",  dbOpenDynaset)

Der nächste Fehler lauert in der Funktion AnmeldungPruefen im Modul mdlBenutzerverwaltung auf uns. Hier tritt der Fehler wieder in einem Aufruf der OpenRecordset-Methode auf. Wir machen kurzen Prozess und passen alle Aufrufe der OpenRecordset-Methode wie oben beschrieben an.

Kennwortgeschützter Zugriff per DLookup

Wenn wir die Schaltfläche cmdAnmelden im Formular frmAnmeldung betätigen, finden wir den nächsten Fehler in der folgenden DLookup-Anweisung:

lngBenutzerID = DLookup("BenutzerID", "tblBenutzer",  "Benutzername = ''" & strBenutzername & "''")

Welche Möglichkeit haben wir hier, um den Zugriff auf die kennwortgeschützte Backend-Datenbank zu ermöglichen Wir probieren es mit der folgenden Variante aus:

lngBenutzerID = DLookup("BenutzerID", BackendPathPassword  & "tblBenutzer", _
     "Benutzername = ''" & strBenutzername  & "''")

Wir fügen also einfach vorn an den zweiten Parameter den Ausdruck mit dem Kennwort und dem Pfad in eckigen Klammern an. Das Ergebnis ist allerdings ernüchternd und es sieht wie in Bild 6 aus.

Fehler beim Versuch, das Kennwort in die DLookup-Abfrage zu integrieren

Bild 6: Fehler beim Versuch, das Kennwort in die DLookup-Abfrage zu integrieren

PLookup statt DLookup

Statt der gewohnten Funktion DLookup verwenden wir eine benutzerdefinierte Version dieser Funktion, die wir PLookup nennen (für Password-Lookup). Zunächst schauen wir uns die kleine Änderung an, die den Aufruf betrifft:

lngBenutzerID = PLookup("BenutzerID", "tblBenutzer",  "Benutzername = ''" & strBenutzername & "''")

Die Funktion PLookup definieren wir im Modul mdlBackend wie folgt:

Public Function PLookup(strField As String,  strTable As String, strCriteria As String) As Variant
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Set db = CurrentDb
     Set rst = db.OpenRecordset("SELECT " & strField  & " FROM " & BackendPathPassword & strTable _
          & " WHERE " & strCriteria, dbOpenDynaset)
     If Not rst.EOF Then
         PLookup = rst.Fields(0)
     End If
End Function

Die Funktion PLookup erwartet die gleichen Parameter wie die eingebaute DLookup-Funktion. Sie erstellt ein neues Recordset, bei dem der Abfrageausdruck aus den zu einer SELECT-Anweisung zusammengesetzten Parametern der Funktion resultiert.

Dabei verwendet die Funktion beim Zusammenstellen der SELECT-Anweisung wieder die Funktion BackendPath-Password, welche den Ausdruck mit dem Kennwort und dem Backend-Pfad in eckigen Klammern und einen abschließenden Punkt liefert.

Sie können nun, ähnlich wie bei der Anpassung der Recordsets, auch alle Aufrufe der DLookup-Funktion anpassen, indem Sie DLookup durch PLookup ersetzen.

Abfragen anpassen

Die Funktion BenutzerBerechtigungen aus dem Modul mdlBenutzerverwaltung verwendet eine SELECT-Anweisung mit einer Abfrage als Datenquelle als Parameter einer OpenRecordset-Methode:

Set rst = db.OpenRecordset("SELECT * FROM  qryBenutzerBerechtigungen WHERE BenutzerID = " _
      & lngBenutzerID & " AND Tabelle = ''" & strTabelle  & "''", dbOpenDynaset)

Die Abfrage qryBenutzerBerechtigungen referenziert logischerweise die Tabellen im Frontend beziehungsweise würde auch noch funktionieren, wenn die Verknüpfungen auf die Tabellen im Backend noch vorhanden wären.

Es gibt aber weder Tabellen noch Verknüpfungen im Frontend, weshalb die Abfrage nicht mehr funktionieren kann. Genau genommen können wir noch nicht einmal mehr den Entwurf ansehen – dies resultiert in der Fehlermeldung aus Bild 7.

Fehler beim Anzeigen der Entwurfsansicht einer Abfrage

Bild 7: Fehler beim Anzeigen der Entwurfsansicht einer Abfrage

Nach dem Schließen der Fehlermeldung erscheint immerhin noch die SQL-Ansicht der Abfrage (siehe Bild 8). Wir haben aber keine Möglichkeit, etwa über eine Eigenschaft festzulegen, dass die Abfrage auf die in der Backend-Datenbank befindlichen Tabellen zugreifen soll.

SQL-Ansicht der Abfrage

Bild 8: SQL-Ansicht der Abfrage

Wir wollen nun für jede Abfrage eine Funktion im Modul mdlBackend hinterlegen, welche den SQL-Code der Abfrage enthält und die Klausel mit dem Pfad und dem Kennwort zur Angabe der Quelltabellen hinzufügt. Dazu vereinfachen wir die Abfrage zunächst, indem wir Alias-Bezeichnungen für die Felder im FROM-Teil angeben und diese in der Feldliste und in den übrigen Bereichen wie den Kriterien und den Sortierungen nutzen. Vorher sieht die Abfrage noch wie folgt aus:

SELECT tblBenutzer.BenutzerID, tblBenutzer.Benutzername, tblTabellen.Tabelle, tblBerechtigungen.Berechtigung, tblBerechtigungszuordnungen.BerechtigungID FROM tblTabellen INNER JOIN (tblBenutzer INNER JOIN ((tblBerechtigungen INNER JOIN (tblBenutzergruppen INNER JOIN tblBerechtigungszuordnungen ON tblBenutzergruppen.BenutzergruppeID = tblBerechtigungszuordnungen.BenutzergruppeID) ON tblBerechtigungen.BerechtigungID = tblBerechtigungszuordnungen.BerechtigungID) INNER JOIN tblGruppenzuordnungen ON tblBenutzergruppen.BenutzergruppeID = tblGruppenzuordnungen.BenutzergruppeID) ON tblBenutzer.BenutzerID = tblGruppenzuordnungen.BenutzerID) ON tblTabellen.TabelleID = tblBerechtigungszuordnungen.TabelleID;

Nach den Änderungen erscheint diese schon etwas aufgeräumter:

SELECT t2.BenutzerID, t2.Benutzername, t1.Tabelle, t3.Berechtigung, t5.BerechtigungID
FROM tblTabellen AS t1 
INNER JOIN (tblBenutzer AS t2 INNER JOIN ((tblBerechtigungen AS t3 INNER JOIN (tblBenutzergruppen AS t4 INNER JOIN tblBerechtigungszuordnungen AS t5 ON t4.BenutzergruppeID = t5.BenutzergruppeID) ON t2.BerechtigungID = t5.BerechtigungID) INNER JOIN tblGruppenzuordnungen AS t6 ON t4.BenutzergruppeID = t6.BenutzergruppeID) ON t2.BenutzerID = t6.BenutzerID) ON t1.TabelleID = t5.TabelleID;

Hier fügen wir nun innerhalb der wie folgt zu definierenden Funktion noch dynamisch den Inhalt der Funktion BackendPathPassword ein (siehe Listing 1). Und das ist auch der Grund, warum wir mit dem Schlüsselwort AS angegebene Alias-Bezeichnungen für die Tabellen arbeiten. Wenn wir immer die vollen Tabellennamen verwenden würden, müssten wir vor jedem Tabellennamen den Inhalt der Funktion BackendPathPassword einfügen. Wichtig ist noch, dass der SQL-String nicht mit einem Semikolon abschließt – gegebenenfalls wollen wir ja noch eine –WHERE– oder ORDER BY-Klausel anhängen.

Public Function qryBenutzerBerechtigungen() As String
     Dim strSQL As String
     strSQL = "SELECT t2.BenutzerID, t2.Benutzername, t1.Tabelle, t3.Berechtigung, t5.BerechtigungID "
     strSQL = strSQL & "FROM " & BackendPathPassword & "tblTabellen AS t1 "
     strSQL = strSQL & "INNER JOIN (" & BackendPathPassword & "tblBerechtigungen AS t3 "
     strSQL = strSQL & "INNER JOIN (" & BackendPathPassword & "tblBenutzer AS t2 "
     strSQL = strSQL & "INNER JOIN ((" & BackendPathPassword & "tblBenutzergruppen AS t4 "
     strSQL = strSQL & "INNER JOIN " & BackendPathPassword & "tblGruppenzuordnungen AS t6 "
     strSQL = strSQL & "ON t4.BenutzergruppeID = t6.BenutzergruppeID) "
     strSQL = strSQL & "INNER JOIN " & BackendPathPassword & "tblBerechtigungszuordnungen AS t5 "
     strSQL = strSQL & "ON t4.BenutzergruppeID = t5.BenutzergruppeID) "
     strSQL = strSQL & "ON t2.BenutzerID = t6.BenutzerID) "
     strSQL = strSQL & "ON t3.BerechtigungID = t5.BerechtigungID) "
     strSQL = strSQL & "ON t1.TabelleID = t5.TabelleID"
     qryBenutzerBerechtigungen = strSQL
End Function

Listing 1: Funktion zum Zusammenstellen der Abfrage qryBenutzerBerechtigungen

Das Ergebnis der Funktion sieht wie in Listing 2 aus.

SELECT t2.BenutzerID, t2.Benutzername, t1.Tabelle, t3.Berechtigung, t5.BerechtigungID 
FROM [;PWD=kennwort;DATABASE=C:...Daten.accdb].tblTabellen AS t1 
INNER JOIN ([;PWD=kennwort;DATABASE=C:...Daten.accdb].tblBerechtigungen AS t3 
INNER JOIN ([;PWD=kennwort;DATABASE=C:...Daten.accdb].tblBenutzer AS t2 
INNER JOIN (([;PWD=kennwort;DATABASE=C:...Daten.accdb].tblBenutzergruppen AS t4 
INNER JOIN [;PWD=kennwort;DATABASE=C:...Daten.accdb].tblGruppenzuordnungen AS t6 
ON t4.BenutzergruppeID = t6.BenutzergruppeID) 
INNER JOIN [;PWD=kennwort;DATABASE=C:...Daten.accdb].tblBerechtigungszuordnungen AS t5 
ON t4.BenutzergruppeID = t5.BenutzergruppeID) 
ON t2.BenutzerID = t6.BenutzerID) 
ON t3.BerechtigungID = t5.BerechtigungID) 
ON t1.TabelleID = t5.TabelleID

Listing 2: Die fertige Abfrage qryBenutzerBerechtigungen

In der Funktion BenutzerBerechtigungen fügen wir die Funktion qryBenutzerBerechtigungen nun wie folgt in die OpenRecordset-Methode ein:

Set rst = db.OpenRecordset(qryBenutzerBerechtigungen  & " WHERE t2.BenutzerID = " & lngBenutzerID _
     & " AND  t1.Tabelle = ''" & strTabelle & "''", dbOpenDynaset)

Hier müssen wir auch noch den Tabellen-Alias für die beiden Parameter eintragen, also t2.BenutzerID statt BenutzerID und t1.Tabelle statt Tabelle. Sie sehen: So richtig komfortabel und einfach ist es nicht, es sind schon einige Anpassungen nötig.

Gebundene Formulare

Bei den gebundenen Formularen, also solchen Formularen, deren Eigenschaft Datensatzquelle die Tabelle oder Abfrage angibt, deren Daten das Formular anzeigen soll, verlieren wir den Komfort, den die Datenbindung bietet. So müssen Sie die Eigenschaft Datensatzquelle leeren und die Bindung an die Tabelle oder Abfrage über die Zuweisung eines Recordset-Objekts an die VBA-Eigenschaft Recordset erledigen.

Dadurch entfällt auch der Komfort, die Felder der Datensatzquelle einfach aus der Feldliste in das Formular zu ziehen.

Andererseits können Sie die Formulare auch zuerst bei aktivierter Datenbindung entwerfen und erst nach der Fertigstellung die Datenbindung entfernen.

Dazu müssten Sie natürlich temporär auch wieder die Tabellen in das Frontend holen – entweder als lokale Tabelle oder als Verknüpfung zur Tabelle im Backend.

Wir gehen an dieser Stelle davon aus, dass etwa das Formular frmKunden aktuell an die Tabelle tblKunden gebunden ist.

In diesem Fall führt das Öffnen des Formulars nach dem Entfernen der Tabellen und Verknüpfungen aus dem Frontend zu der Fehlermeldung Die auf diesem Formular oder in diesem Bericht angegebene Datensatzquelle “tblKunden“ ist nicht vorhanden.

Danach wird das Formular in der Entwurfsansicht angezeigt, wo auch gleich alle gebundenen Felder markiert sind. Ein Klick auf die Schaltfläche mit dem Ausrufezeichen aus Bild 9 zeigt den Grund für die Markierung – die für die Eigenschaft Steuerelementinhalt angegebenen Felder sind nicht in der Feldliste vorhanden.

Meldung bei Steuerelementen mit fehlerhaftem Steuerelementinhalt

Bild 9: Meldung bei Steuerelementen mit fehlerhaftem Steuerelementinhalt

Datensatzquelle ersetzen

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar