JBoss Seam – formularz zmiany hasła
Ostatnio opisywałem problem wygasania hasła, jednak nie przedstawiłem rozwiązania dotyczącego samej zmiany hasła. Tym razem zamierzam dokładnie opisać komponent realizujący to zadanie. Najprościej będzie wygenerować nowy formularz Seam, a następnie dostosować go do wymagań funkcjonalnych.
Na początek interfejs lokalny komponentu – ChangePassword. Najważniejsze są właściwości – login, stare hasło, nowe hasło i powtórzone nowe hasło (login, oldPassword, password i repeatedPassword) – oraz metody init (wywoływana przy tworzeniu komponentu), destroy (wywoływana przy usuwaniu komponentu) i changePassword (realizująca zmianę hasła):
@Local
public interface ChangePassword {
public String getLogin();
public void setLogin(String login);
public String getOldPassword();
public void setOldPassword(String password);
public String getPassword();
public void setPassword(String password);
public String getRepeatedPassword();
public void setRepeatedPassword(String password);
public void init();
public void destroy();
public void changePassword();
}
Teraz pora na komponent realizujący powyższy interfejs:
@Stateful
@Name("changePassword")
public class ChangePasswordBean
implements ChangePassword {
}
Na początku zdefiniuję stałą, która będzie przechowywać nazwę zdarzenia dotyczącego zmiany hasła (użytego w pliku components.xml):
private static final String EVENT_USER_PASSWORD_CHANGED =
"pl.info.czerwinski.logintest.userPasswordChanged";
Do komponentu wstrzyknę parę obiektów – statusMessages do wyświetlania informacji na stronie, magazyn tożsamości i tożsamość:
@In private StatusMessages statusMessages; @In private IdentityStore identityStore; @In private Identity identity;
Następnie dodam implementację właściwości interfejsu oraz odpowiednie pola:
private String login; private String oldPassword; private String password; private String repeatedPassword;
Podczas tworzenia komponentu (adnotacja org.jboss.seam.annotations.Create) przyda się automatyczne ustawianie nazwy użytkownika (loginu), przechowywanej w tożsamości:
@Create
@Override
public void init() {
this.login =
identity.getCredentials().getUsername();
}
Podczas usuwania komponentu (adnotacje org.jboss.seam.annotations.Destroy i javax.ejb.Remove) wszystkie właściwości będą czyszczone:
@Destroy
@Remove
@Override
public void destroy() {
this.login = null;
this.oldPassword = null;
this.password = null;
this.repeatedPassword = null;
}
Na koniec zostawiłem najważniejszą metodę – changePassword:
public void changePassword() {
if (password.equals(repeatedPassword)) {
if (identityStore.authenticate(
login, oldPassword)) {
identityStore.changePassword(
login, password);
statusMessages.add(
"Hasło zostało zmienione.");
if (identity.isLoggedIn())
identity.logout();
identity.getCredentials().setUsername(
login);
identity.getCredentials().setPassword(
password);
identity.login();
} else {
statusMessages.add(Severity.ERROR,
"#{org.jboss.seam.loginFailed}");
}
} else {
statusMessages.add(Severity.ERROR,
"#{pl.info.czerwinski.errPassRepeat}");
}
if (Events.exists()) {
Events.instance().raiseEvent(
EVENT_USER_PASSWORD_CHANGED, this);
}
}
Na początku porównywane są obie wersje nowego hasła (muszą być takie same) – w przypadku błędu wyświetlana jest wiadomość o błędzie, zapisana w pliku messages_??.properties (gdzie ?? to kod języka), na przykład:
pl.info.czerwinski.errPassRepeat=Obie wersje nowego hasła są różne
Idąc „na skróty” wykorzystałem magazyn tożsamości (identityStore), przy pomocy którego sprawdzam, czy login i stare hasło są prawidłowe (metoda authenticate). W przypadku błędnego uwierzytelnienia wyświetlam stosowną wiadomość.
Jeżeli uwierzytelnienie przebiegnie pomyślnie, zmieniam hasło używając magazynu tożsamości, po czym przelogowuję użytkownika korzystając z nowego hasła.
Na sam koniec zgłaszam zdarzenie, aby powrócić na stronę, z której użytkownik został przekierowany (plik components.xml).
Pozostało mi już tylko uzupełnienie strony związanej z formularzem:
<ui:define name="body">
<h:form id="changePasswordForm">
<rich:panel>
<f:facet name="header">Zmiana hasła</f:facet>
<s:decorate id="loginField"
template="layout/edit.xhtml">
<ui:define name="label">
Login
</ui:define>
<h:inputText id="value" required="true"
value="#{changePassword.login}"/>
</s:decorate>
<s:decorate id="oldPasswordField"
template="layout/edit.xhtml">
<ui:define name="label">
Stare hasło
</ui:define>
<h:inputSecret id="value" required="true"
value="#{changePassword.oldPassword}"/>
</s:decorate>
<s:decorate id="passwordField"
template="layout/edit.xhtml">
<ui:define name="label">
Nowe hasło
</ui:define>
<h:inputSecret id="value" required="true"
value="#{changePassword.password}"/>
</s:decorate>
<s:decorate id="repeatedPasswordField"
template="layout/edit.xhtml">
<ui:define name="label">
Powtórz nowe hasło
</ui:define>
<h:inputSecret id="value" required="true"
value="#{changePassword.repeatedPassword}"/>
</s:decorate>
</rich:panel>
<div class="actionButtons">
<h:commandButton id="changePassword"
value="Wprowadź zmiany"
action="#{changePassword.changePassword}"/>
</div>
</h:form>
</ui:define>
Ważne jest, aby dla wszystkich trzech haseł użyć znacznika h:inputSecret zamiast h:inputText, dzięki czemu zamiast liter w polu formularza będą się pojawiać czarne kółka.
W powyższym przykładzie znajduje się jeden słaby punkt – hasło zmieniane jest przy wykorzystaniu klasy EnhancedJpaIdentityStore, do której dostęp nie wymaga autoryzacji. Cóż… przy takiej konstrukcji program jest bezpieczny, ale może warto zastosować jakieś mechanizmy – tak na wszelki wypadek? Lecz o tym napiszę już kiedy indziej.



