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ń.


Wartościowy wpis! Można kolejny na temat “wywołać odpowiednio zdarzenia System.Permission.Removed lub System.Permission.Removed”? Poczekam cierpliwie.
@Jacek
Nie ma sprawy – napiszę post o wywoływaniu i obsłudze zdarzeń w JBoss Seam, który będzie zawierał przykład użycia powyższych zdarzeń.
Przydatny materiał.
Jednak do metody hasPermission należy dodać kontrolę parametru “action” ponieważ w obecnej postaci metoda zawsze zwróci true w przypadku tego samego “target” z innymi “action”.
@Mirek
Nie ma to jak sprawne oko! Dzięki za znalezienie tego błędu, bo faktycznie mógł on powodować poważne skutki, kod został poprawiony.
Hey Marek,
I am trying your example, I have some issues: I debugged the code and it seems that it actually checks the permissions everytime I try to load the page with hasPermission, from somewhere it hist the permission model table. BUT in only the case when it doesnot have any permission does it come into this permissionresovler and run the hasPermission method.
Like user 1 has permissions to update,list and add but does not have the permission to delete, now My page has permission on delete button only, so each time I try to load the page with delete button first it checks if the user has permission to delete (from somewhere) and if yes it doesn’t come into my hasPermission method, if it does not have permission then it comes into my hasPermission method to recheck.
So what I am trying to say is that it actually does check with DB and hits the permission table to retrieve the permission for the user at every point even after having this resolver. Which component is doing that is beyond my understanding.
Did you see this. Why is my resolver’s hasPermission method not the first one to kick in and some other class hits the db and only in one case does it come into my class. What could be wrong. I will really appreciate your help.
Syed…