Realizacja funkcji bezpieczeństwa w aplikacjach

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.