W sytuacji, gdy programy korzystają z zasobów sieci Internet, bardzo ważne stają się problemy związane z zapewnieniem bezpieczeństwa naszego systemu. Java dostarcza wielu zabezpieczeń systemu i danych przed niepożądanym dostępem lub nieuprawnionymi modyfikacjami. Jednym z tych zabezpieczeń są tzw. zarządcy bezpieczeństwa (ang. security managers). Zarządca bezpieczeństwa implementuje i realizuje zasady bezpieczeństwa (ang. security policy) dla aplikacji.
Każda aplikacja może mieć swój obiekt zarządcy bezpieczeństwa, który w czasie całego czasu jej wykonania kontroluje realizację zasad bezpieczeństwa. W pakiecie java.lang zdefiniowano abstrakcyjną klasę SecurityManager dostarczającą interfejsu programistycznego i częściowej implementacji zarządców bezpieczeństwa Javy.
Domyślnie, aplikacja nie ma zarządcy bezpieczeństwa, ponieważ środowisko wykonawcze Javy nie tworzy automatycznie zarządcy bezpieczeństwa dla każdej aplikacji Javy, więc wszystkie operacje, które podlegają ograniczeniom związanym z bezpieczeństwem są dopuszczalne. Aby aplikacja miała swojego zarządcę bezpieczeństwa, musi zostać stworzony i zainstalowany obiekt zarządcy bezpieczeństwa aplikacji.
Uwaga:
Zarządca bezpieczeństwa może zostać zainstalowany tylko raz w
trakcie wykonania aplikacji. Wszystkie przeglądarki stron WWW
tworzą swojego zarządcę bezpieczeństwa wtedy, gdy są
uruchamiane. Ograniczenia apletu zależą więc od zarządcy
bezpieczeństwa programu, w którym aplet został uruchomiony.
Aplety nie mogą instalować swojego zarządcy bezpieczeństwa,
ponieważ został on już zainstalowany. Każda próba,
zainstalowania zarządcy bezpieczeństwa przez aplet powoduje
wygenerowanie wyjątku typu SecurityException.
Gdy definiujemy zarządcę bezpieczeństwa,
zależnie od ograniczeń, jakie mają być nałożone na
aplikację, redefiniujemy odpowiednie metody z klasy SecurityManager.
W poniższej tabeli zestawiono obiekty, na których mogą być
dokonywane różnego rodzaju operacje oraz metody, pozwalające
na (lub zabraniające) ich wykonanie.
Podmiot operacji | Zatwierdzana przez metodę |
gniazdko | checkAccept(String host, int port) checkConnect(String host, int port) checkConnect(String host, int port, Object executionContext) checkListen(int port) |
wątki | checkAccess(Thread thread) checkAccess(ThreadGroup threadgroup) |
ładowanie klas | checkCreateClassLoader() |
system plików | checkDelete(String filename) checkLink(String library) checkRead(FileDescriptor filedescriptor) checkRead(String filename) checkRead(String filename, Object executionContext) checkWrite(FileDescriptor filedescriptor) checkWrite(String filename) |
polecenia systemowe | checkExec(String command) |
interpreter | checkExit(int status) |
pakiet | checkPackageAccess(String packageName) checkPackageDefinition(String packageName) |
właściwości | checkPropertiesAccess() checkPropertyAccess(String key) checkPropertyAccess(String key, String def) |
sieć | checkSetFactory() |
okna | checkTopLevelWindow(Object window) |
Tabela 2-11 Zestawienie metod klasy SecurityManager.
Domyślna implementacja wszystkich metod klasy SecurityManager przedstawionych w tabeli jest następująca:
public void checkXXX(. . .) { throw new SecurityException(); }
Oznacza to, że dla każdej próby wykonania metody, która nie została zredefiniowana, wystąpi wyjątek typu SecurityException. Po redefinicji metody z klasy SecurityManager powinna ona działać w następujący sposób:
Zdefiniujmy przykładowego zarządcę bezpieczeństwa, który kontroluje zapis i odczyt z sytemu plików. W tym celu redefiniujemy tylko metody, odpowiadające za odczyt i zapis do sytemu plików. W konstruktorze klasy SprawdzDostep jako argument podane zostaje hasło, po wprowadzeniu którego operacje odczytu i zapisu mogą zostać przeprowadzone. W klasie tej zdefiniowano także metodę jestDostep, która prosi o podanie hasła, a następnie porównuje je z hasłem przechowywanym w polu danych m_haslo.
Przykład 2.51 Definicja zarządcy bezpieczeństwa SprawdzDostep
import java.io.*; class SprawdzDostep extends SecurityManager { private String m_haslo; SprawdzDostep(String m_haslo) { super(); this.m_haslo = m_haslo; } public void checkRead(FileDescriptor filedescriptor) { if (!jestDostep()) throw new SecurityException("Brak mozliwosci odczytu (1) z " + filedescriptor); } public void checkRead(String filename) { if (!jestDostep()) throw new SecurityException("Brak mozliwosci odczytu (2) z " + filename); } public void checkRead(String filename, Object executionContext) { if (!jestDostep()) throw new SecurityException("Brak mozliwosci odczytu (3) z " + filename); } public void checkWrite(FileDescriptor filedescriptor) { if (!jestDostep()) throw new SecurityException("Brak mozliwosci zapisu (1) do " + filedescriptor); } public void checkWrite(String filename) { if (!jestDostep()) throw new SecurityException("Brak mozliwosci zapisu (2) do " + filename); } // definicja pomocniczej metody, która prosi o podanie hasła // a następnie sprawdza je z hasłem przechowywanym w polu m_haslo private boolean jestDostep() { DataInputStream dis = new DataInputStream(System.in); String podaneHaslo; System.out.println("Podaj haslo:"); try { podaneHaslo = dis.readLine(); if (podaneHaslo.equals(m_haslo)) return true; else return false; } catch (IOException e) { return false; } } }
W celu sprawdzenia działania zarządcy bezpieczeństwa zdefiniowano aplikację przedstawioną w poniższym przykładzie. W aplikacji tej jako zarządca bezpieczeństwa ustawiany jest zarządca SprawdzDostep. W przypadku próby odczytu lub zapisu wykonywana jest odpowiednia metoda zadeklarowana w klasie SprawdzDostep.
Przykład 2.52 Definicja klasy TestBezpieczenstwaPlikow
import java.io.*; class TestBezpieczenstwaPlikow { public static void main(String[] args) { try { System.setSecurityManager(new SprawdzDostep("KARI"));} catch (SecurityException se) { System.err.println("Zarzadcy bezpieczenswa nie ustawiono!");} try { DataInputStream fis = new DataInputStream(new FileInputStream("plikwe.txt")); DataOutputStream fos = new DataOutputStream(new FileOutputStream("plikwy.txt")); String daneWejsciowe; while ((daneWejsciowe = fis.readLine()) != null) { fos.writeBytes(daneWejsciowe); fos.writeByte('\n'); } fis.close(); fos.close(); } catch (IOException ioe) { System.err.println("Blad I/O w TestBezpieczenstwaPlikow."); ioe.printStackTrace(); } } }
Po uruchomieniu aplikacji TestBezpieczenstwaPlikow, przy tworzeniu nowego obiektu typu FileInputStream, wykonywana jest metoda checkRead i jeśli wprowadzimy nieprawidłowe hasło, to zostanie wygenerowany wyjątek. Sytuację taką przedstawia poniższa ilustracja.
Ilustracja 2-31 Wynik wykonania aplikacji TestBezpieczenstwaPlikow
Przy projektowaniu zarządcy bezpieczeństwa należy pamiętać, iż metody sprawdzające zasady bezpieczeństwa mogą być wołane w wielu sytuacjach, dlatego, gdy redefiniujemy metodę należy się upewnić, że rozumiemy wszystkie sytuacje, w których metoda może zostać wywołana.