sobota, 8 września 2007

Tworzenie własnego modułu logowania w środowisku JBoss

Tytułem wstępu: jeśli myślisz, że potrzebujesz zbudować swój moduł logowania
to prawdopodobnie nie znasz możliwości JBoss'a w tym względzie.
W JBoss istnieje szereg przygotowanych modułów logowania,
które możesz połączyć w odpowiednią politykę aplikacji (ang. application policy).
Nie mniej czasem zachodzi potrzeba napisania własnego modułu.
Nie wnikam, nie osądzam, omawiam...

Najpierw tworzymy klasę rozszerzającą AbstractServerLoginModule:


package copernicus.blog.jboss.auth;

import java.io.IOException;
import java.security.Principal;
import java.security.acl.Group;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import org.apache.log4j.Logger;
import org.jboss.security.SecurityAssociation;
import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.spi.AbstractServerLoginModule;

public class DomenaBezpieczenstwa extends AbstractServerLoginModule
{
private Logger log = Logger.getLogger(DomenaBezpieczenstwa.class);
private Principal uzytkownik;
private String haslo;

@Override
protected Principal getIdentity() {
return uzytkownik;
}

@Override
protected Group[] getRoleSets() throws LoginException {

// wszystkie role uzytkownika powinny byc umieszczone w tej grupie
SimpleGroup roles = new SimpleGroup("Roles");

roles.addMember(new SimpleGroup("rola"));

return new Group[]{roles};
}

@Override
public boolean login() throws LoginException {
this.loginOk = false;

uzytkownik = SecurityAssociation.getPrincipal();
haslo = String.valueOf(SecurityAssociation.getCredential());

if(uzytkownik == null || haslo == null)
{
NameCallback wyciskaczLoginu = new NameCallback("Podaj login: ");
PasswordCallback wyciskaczHasla = new PasswordCallback("Podaj haslo", false);
try
{
this.callbackHandler.handle(new Callback[]{wyciskaczLoginu, wyciskaczHasla});
uzytkownik = new SimplePrincipal(wyciskaczLoginu.getName());
char [] passChars = wyciskaczHasla.getPassword();
haslo = String.valueOf(passChars, 0, passChars.length);
}
catch (IOException e)
{
e.printStackTrace();
}
catch (UnsupportedCallbackException e)
{
e.printStackTrace();
}
}

if(uzytkownik != null && haslo != null)
{
// tutaj mozna wlasny mechanizm sprawdzania zamontowac
this.loginOk = true;
log.info(String.format("Zalogowal sie uzytkownik: [%s] z haslem: [%s]", uzytkownik, haslo));
}
else
{
// jesli logowanie sie nie powiedzie, nalezy zrzucic wyjatek LoginException
throw new LoginException(String.format("[%s]/[%s]", uzytkownik, haslo));
}

public void commit()
{
boolean result = super.commit();
SecurityAssociation.setPrincipal(this.uzytkownik);
SecurityAssociation.setCredential(this.haslo);
return result;
}

return super.loginOk;
}
}


I już. Aby skompilować powyższy fragment będziesz potrzebować niektórych bibliotek z katalogu JBoss.

Omówię może co się dzieje w poszczególnych metodach -wszak Ty jako klient modułu nie będziesz ich wywoływał. Zrobi to za Ciebie JBoss.

W pierwszej kolejności wywołana zostanie - nie licząc konstruktora, który musi być bezargumentowy - metoda initialize(). Metoda ta ma za zadanie zainicjować następujące pola: subject, callbackHandler, options, sharedStateMap. Doskonale robi to metoda z klasy nadrzędnej, więc nie musimy jej implementować. Następnie JBoss wywoła metodę login(). Tutaj cały jest ambaras :-). Jej implementacje mogą być naprawdę różne. Ja oczekuję, że informacje uwierzytelniające znajdą się w "schowku" SecurityAssociation. Ale prawidłowa implementacja powinna zawierać także wyciągnięcie danych uwierzytelniających z callbackHandler. Dlaczego? Ponieważ często będzie się zdarzało, że ludzie będą traktowali moduł logowania jako... moduł logowania, mimo że jest to tak naprawdę moduł określający domenę bezpieczeństwa. Jak to zrobić? Zajrzyj w kod.

UWAGA: w JBoss możesz wykorzystać tak naprawdę tylko trzy typy Callback'ów: NameCallback, PasswordCallback oraz ObjectCallback. Ten ostatni może jednak przechowywać dowolny obiekt. Jeśli spróbujesz być szczwany i napiszesz własny Callback, który może być nawet prawidłową implementacją tego interfejsu - JBoss zagra Ci na nosie i powie, że nie wspiera takiego Callbacka.

Dysponując już nazwą użytkownika i jego hasłem (czy np. innym obiektem uwierzytelniającym, np. certyfikatem) robisz co Ci się żywnie podoba. Co dalej? Jeśli Twój moduł jest jednym z wielu modułów w domenie i niepowodzenie w przypadku niezalogowania ma nie być ostateczne, to wtedy ustawiasz wartość pola this.loginOk na false i takiż zwracasz rezultat. Jeśli wszystko jest w porządku - naturalnie zwracasz true, ale koniecznie trzeba this.loginOk ustawić na true. Rzucenie wyjątku LoginException jest tożsame z nieudanym logowaniem. I na tym kończy się etap pierwszy.

Etap drugi realizuje zatwierdzenie lub odrzucenie logowania. Metoda commit() powinna w naszym przypadku umieścić dane uwierzytelniające w obiekcie subject (robi to doskonale metoda commit() z klasy nadrzędnej) ORAZ umieścić informacje w SecurityAssociation. Podczas wywoływania metody super.commit() wywoływana jest metoda getIdentity(), także nie zwracajcie tam null'a :-). Aby określić w jakiej roli występuje użytkownik JBoss wywołuje metode getRoleSet(). Należy role umieścić w grupie o nazwie Roles, inaczej JBoss nie rozpozna intencji programisty.

Drugi etap może zakończyć się przerwaniem operacji uwierzytelniania, czyli wywołaniem metody abort(). Należy w takim wypadku wyczyścić SecurityAssociation oraz subject. Nie zamieszczałem już tego w kodzie, ale opisałem :-).

W razie wszelkich pytań z chęcią wyjaśnię więcej szczegółów związanych z logowaniem w JBoss.

Pytanie do Was: czy znacie jakieś serwisy z blogiem dla programistów, gdzie kod źródłowy można przedstawiać w ładnej formie? Na tej stronie próbuję używać Highlighter'a z Google Code, ale nie bardzo chce to działać... Szkoda, bo wolałbym w ładniejszej formie prezentować w przyszłości kawałki kodu.

Brak komentarzy: