Maj
28
2009

Problem z wygasaniem hasła w Seam

Słowa kluczowe: , | Kategorie: Seam Framework
No Gravatar

W związku z ustawą z dnia 29 sierpnia 1997 roku (Dz.U. 1997 Nr 133 poz. 883), w aplikacjach przetwarzających dane osobowe pojawiła się potrzeba wymuszenia zmiany hasła użytkownika co 30 dni. Problem wygaśnięcia hasła opisał na forum Seam Raimund Hölle, jednak zastosowanie się do jego wskazówek nie było takie proste.

1. Przeciążenie magazynu tożsamości

Pierwszym krokiem zalecanym przez Raimunda Hölle było przeciążenie magazynu tożsamości, co omówiłem w poprzednim wpisie. Zatem dodałem do mojej encji User kolejne pole, opatrzone nową adnotacją:

@Target({METHOD,FIELD})
@Documented
@Retention(RUNTIME)
@Inherited
public @interface UserPasswordExpiryDate {
}
@Column(name = "expiry_date")
@Temporal(TemporalType.DATE)
@UserPasswordExpiryDate
protected Date expiry;

Następnie uwzględniłem to pole w klasie EnhancedJpaIdentityStore:

private AnnotatedBeanProperty<UserPasswordExpiryDate>
    userPassExpiryProperty;

Analogicznie do wcześniejszych rozszerzeń, dodałem odpowiednie linijki kodu w metodzie init:

this.userPassExpiryProperty =
    new AnnotatedBeanProperty<UserPasswordExpiryDate>(
            this.getUserClass(),
            UserPasswordExpiryDate.class);

oraz authenticate:

if (this.userPassExpiryProperty != null) {
    identity.setExpiryDate((Date)
            this.userPassExpiryProperty.getValue(user));
}

W ostatnim kroku dodałem jeszcze ustawianie nowej daty wygaśnięcia hasła przy jego zmianie:

private static final long millisPerMonth = 2592000000L;

@Override
public boolean changePassword(String username, String password) {
    boolean success = super.changePassword(username, password);
    if (success) {
        if (this.userPassExpiryProperty != null) {
            Date date = new Date(
                    System.currentTimeMillis() +
                    millisPerMonth);
            Object user = this.lookupUser(username);
            EnhancedIdentity identity =
                EnhancedIdentity.instance();
            this.userPassExpiryProperty.setValue(
                    user, date);
            this.mergeEntity(user);
            identity.setExpiryDate(date);
        }
    }
    return success;
}

2. Przeciążenie tożsamości

Następnym krokiem Raimunda Hölle jest przeciążenie tożsamości. Wykorzystam w tym celu moją klasę EnhancedIdentity.

Najpierw dodam pole i właściwość expiryDate, aby przechowywać datę wygaśnięcia hasła:

private Date expiryDate;

public Date getExpiryDate() {
    return this.expiryDate;
}

public void setExpiryDate(Date expiry) {
    this.expiryDate = expiry;
}

Wewnątrz tożsamości definiuję nazwę zdarzenia oznaczającego wygaśnięcie hasła:

private static final String EVENT_USER_PASSWORD_EXPIRED =
    "pl.info.czerwinski.logintest.userPasswordExpired";

Jednak w metodzie postAuthenticate pojawiają się problemy. Raimund Hölle zaleca zgłoszenie wyjątku, ale przy przesłanianiu metody klasy nadrzędnej nie mogę zdefiniować dla niej nowych wyjątków (throws …), podczas gdy dodanie bloku try…catch wewnątrz postAuthenticate sprawiłoby, że cała operacja straciłaby sens. Dlatego ograniczyłem się do zgłoszenia samego zdarzenia:

@Override
protected void postAuthenticate() {
    if (this.getExpiryDate().before(
            new Date(System.currentTimeMillis()))) {
        if (Events.exists()) {
            Events.instance().raiseEvent(
                    EVENT_USER_PASSWORD_EXPIRED,
                    this);
        }
    }
    super.postAuthenticate();
}

Wyjątek zgłaszam dopiero w metodzie obserwatora (@org.jboss.seam.annotations.Observer) zdarzenia pl.info.czerwinski.logintest.userPasswordExpired:

@Observer(EVENT_USER_PASSWORD_EXPIRED)
public void throwUserPasswordExpiredException()
throws UserPasswordExpiredException {
    throw new UserPasswordExpiredException(
            "Hasło wygasło!");
}

3. Pliki components.xmlpages.xml

Stosując się do zaleceń Raimunda Hölle dodałem też obsługę zdarzenia i wyjątku do plików components.xml:

<event type="pl.info.czerwinski.logintest.userPasswordExpired">
  <action execute="#{redirect.captureCurrentView}"/>
</event>
<event type="pl.info.czerwinski.logintest.userPasswordChanged">
  <action execute="#{redirect.returnToCapturedView}"/>
</event>

oraz pages.xml:

<exception class="pl.info.czerwinski.logintest.UserPasswordExpiredException">
  <redirect view-id="/changePassword.xhtml">
    <message>Hasło wygasło!</message>
  </redirect>
</exception>

4. Konsternacja

Gdy dodałem komponent ChangePasswordBean oraz stronę changePassword.xhtml (chwilowo pominę szczegóły implementacji – napiszę tylko że to zwykły formularz), natrafiłem na istotny problem. Zgłoszenie wyjątku powoduje, że uwierzytelnienie nie powiodło się – użytkownik nie posiada uprawnień do zmiany hasła. Muszę zastosować klasę org.jboss.seam.security.RunAsOperation, aby w ogóle móc zmienić hasło.

Jednak tutaj powstaje kolejne niebezpieczeństwo – podczas zmiany hasła użytkownik nie jest uwierzytelniony! Oznacza to, że każdy może zmienić każde hasło, a następnie zalogować się na cudze konto.

5. Rozwiązanie problemu

Po dłuższym zastanowieniu przyszły mi do głowy dwie możliwości rozwiązania tego problemu:

  1. Zamiast zgłaszania zdarzenia lub wyjątku, mogę przypisywać użytkownikowi rolę passwordExpired albo passwordValid… trochę naokoło.
  2. Na formularzu powinienem wymagać podania identyfikatora użytkownika (loginu), starego hasła oraz nowego hasła (dwukrotnie).

Następnym razem opiszę ten drugi sposób i postaram się znaleźć wszystkie jego słabe strony (bądź wykazać, że takich nie ma), aby uzyskać pewność, że aplikacja będzie bezpieczna.

Napisz Komentarz

*