JBoss Seam w swojej najnowszej odsłonie (2.1.0.SP1) w porównaniu z wersją 2.0 posiada bardzo wiele zmian związanych z bezpieczeństwem. Omówione one zostały na przykładzie aplikacji SeamSpace w poście na blogu Shane’a Bryzaka – głównego deweolpera kwestii bezpieczeństwa w JBoss Seam. Osobom zainteresowanym polecam również przestudiować rozdział poświęcony bezpieczeństwu w oficjalnej dokumentacji Seama.

Nowością w Seam 2.1 jest możliwość definiowania uprawnień i przypisywania ich określonym rolom. Jest to rozwiązanie bardzo elastyczne, jak również bezpieczne, a co najważniejsze – wygodne! Postanowiłem sprawdzić czy sprawdza się to równiez w praktyce.

W swojej aplikacji postawiłem wykorzystać JpaPermissionStore, jak również JpaIdentityStore aby móc zapisywać uprawnienia i użytkowników w relacyjnej bazie danych. Po odpowiedniej konfiguracji (więcej info w poście na blogu Shane’a) oraz zabezpieczeniu odpowiednich stron okazało się, że każdorazowo (sic!) gdy jest sprawdzane jakieś uprawnienie – wykonywane jest zapytanie do bazy danych! W przypadku wielokrotnego sprawdzania uprawnień na jednej stronie (a ma to miejsce praktycznie zawsze) dochodziło do wykonywania nawet kilkudziesięciu zapytań – bardzo często takich samych.

Oczywiście trzeba było to zneutralizować. Jak? Nadpisując komponent org.jboss.seam.security.permission.PersistentPermissionResolver w ten sposób:

package pl.atlone.firma.auth;

import java.util.HashMap;
import java.util.Map;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Identity;
import org.jboss.seam.security.permission.PersistentPermissionResolver;

@Scope(ScopeType.APPLICATION)
@BypassInterceptors
@Name("org.jboss.seam.security.persistentPermissionResolver")
@Install(precedence = Install.APPLICATION)
@Startup
public class SystemPermissionResolver extends PersistentPermissionResolver {

	private static final long serialVersionUID = -9063212676529968783L;

	@Logger
	private Log log;

	private Map<string , Map<String, String>> permissions = new HashMap</string><string , Map<String, String>>();

	@Observer( { "System.Permission.Removed", "System.Permission.Updated" })
	public void onSecurityUpdate() {
		permissions.clear();
	}

	@Override
	public boolean hasPermission(Object target, String action) {

		log.trace("Checking permissions: [identity = "
				+ Identity.instance().getCredentials().getUsername()
				+ "], [target = " + target + "]. [action = " + action + "]");

		Map</string><string , String> identityPermissions = permissions.get(Identity
				.instance().getCredentials().getUsername());

		if (identityPermissions == null) {
			identityPermissions = new HashMap</string><string , String>();
			permissions.put(Identity.instance().getCredentials().getUsername(),
					identityPermissions);
		}

		String permission = identityPermissions.get(target.toString());

		if (permission != null && permission.equals(action)) {
			return true;
		} else {
			boolean hasPermission = super.hasPermission(target, action);

			if (hasPermission) {
				identityPermissions.put(target.toString(), action);
				return true;
			}
		}

		return false;
	}
}

Otrzymujemy w ten sposób komponent pobierający informacje z bazy danych tylko wtedy, gdy żądana informacja nie została już wcześniej pobrana. Komponent bardzo prosty i przyjemny. Należy pamiętać, aby po usunięciu lub uaktualnieniu uprawnienia wywołać odpowiednio zdarzenia System.Permission.Removed lub System.Permission.Removed, co spowoduje wyczyszczenie listy uprawnień znajdujących się w pamięci, a zarazem wymusi załadowanie nowych, uaktualnionych uprawnień.