Proste uwierzytelnienie z JBoss Seam
Najprostrzą metodą uwierzytelnienia użytkownika z użyciem JBoss Seam jest użycie komponentu Authenticator, znajdującego się w wygenerowanym pustym projekcie. Ten sposób daje stosunkowo małe możliwości, więc go przemilczę.
Podstawą do uwierzytelniania użytkowników jest zarządzanie tożsamością z zastosowaniem komponentu IdentityManager. Pozwala on na wykonywanie takich operacji jak dodawanie czy usuwanie użytkowników, przyznawanie i usuwanie ról, a także samo uwierzytelnianie.
Sam menedżer pozostawię na razie na boku, a zajmę się komponentem IdentityStore, który zapewnia dostęp do tożsamości uprawnionych użytkowników systemu. W mojej prostej aplikacji wykorzystam jego standardową (i domyślną) implementację – JpaIdentityStore. Pobiera ona informacje o użytkownikach i ich rolach z bazy danych, biorąc pod uwagę odpowiednie adnotacje do pól i metod klas encji.
Po utworzeniu nowego projektu, dodaję nowe encje użytkowników i ról. Najpierw podam zapytania służące do utworzenia odpowiednich tabel w bazie danych:
CREATE TABLE users
(
id bigint NOT NULL,
"login" character varying(32) NOT NULL,
"password" character varying(32) NOT NULL,
CONSTRAINT pk_users PRIMARY KEY (id)
)
CREATE TABLE roles
(
id bigint NOT NULL,
"name" character varying(32) NOT NULL,
CONSTRAINT pk_roles PRIMARY KEY (id)
)
CREATE TABLE users_roles
(
id_users bigint NOT NULL,
id_roles bigint NOT NULL,
CONSTRAINT pk_users_roles
PRIMARY KEY (id_users, id_roles),
CONSTRAINT fk_users_roles_role
FOREIGN KEY (id_roles)
REFERENCES roles (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_users_roles_user
FOREIGN KEY (id_users)
REFERENCES users (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Tworzenie klasy encji oraz związki encji omówiłem we wcześniejszych wpisach, więc pomijam wyjaśnienia dotyczące samego mapowania obiektowo-relacyjnego.
Encja User
Na początek sama klasa i zaimportowane pakiety:
package pl.info.czerwinski.logintest;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;
import org.jboss.seam.annotations.
security.management.UserPassword;
import org.jboss.seam.annotations.
security.management.UserPrincipal;
import org.jboss.seam.annotations.
security.management.UserRoles;
@Entity
@Table(name = "users")
public class User implements java.io.Serializable {
private static final long serialVersionUID = 1L;
}
Na szczególną uwagę zasługuje pakiet org.jboss.seam.annotations.security.management, w którym znajdują się adnotacje (omówione w dalszej części) powiązane z zarządzaniem tożsamością.
Klucz główny – bez komentarza:
@Id @GeneratedValue @Column(name = "id") @NotNull protected long id;
Identyfikator użytkownika (bądź login) – oznaczony adnotacją @UserPrincipal:
@Column(name = "login") @NotNull @Length(max = 32) @UserPrincipal protected String login;
Hasło użytkownika z adnotacją @UserPassword z atrybutem hash równym none – brak szyfrowania haseł:
@Column(name = "password") @NotNull @Length(max = 32) @UserPassword(hash = "none") protected String password;
Role użytkownika (relacja wiele do wielu) oznaczone adnotacją @UserRoles:
@ManyToMany
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(
name = "id_users",
referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "id_roles",
referencedColumnName = "id"))
@UserRoles
protected Set<Role> roles = new HashSet<Role>(0);
Na koniec podstawowe konstruktory klasy:
public User() {
}
public User(String login, String password) {
this.login = login;
this.password = password;
}
Najważniejsze z punktu widzenia komponentu JpaIdentityStore są adnotacje @UserPrincipal, @UserPassword i @UserRoles. Pozwolą one na rozpoznanie pól (albo właściwości) zawierających dane o użytkowniku.
Encja Role
Kolejna encja przechowuje rolę użytkownika w systemie:
package pl.info.czerwinski.logintest;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;
import org.jboss.seam.annotations.
security.management.RoleName;
@Entity
@Table(name = "roles")
public class Role implements java.io.Serializable {
private static final long serialVersionUID = 1L;
}
Klucz główny encji:
@Id @GeneratedValue @Column(name = "id") @NotNull protected long id;
Nazwa roli – opisana adnotacją @RoleName:
@Column(name = "name") @NotNull @Length(max = 32) @RoleName protected String name;
Konstruktory encji:
public Role() {
}
public Role(String name) {
this.name = name;
}
Tym razem istotna jest jedynie adnotacja @RoleName, która informuje, że dane pole (dana metoda) zawiera nazwę roli użytkownika.
Plik components.xml
Zanim opisywany sposób uwierzytelniania zacznie działać, trzeba jeszcze dokonać paru kosmetycznych zmian w pliku components.xml. Może się on znajdować w katalogu resources/WEB-INF/ albo WebContent/WEB-INF – w zależności od użytego generatora (seam-gen albo JBoss Tools).
W pliku należy odnaleźć następujący znacznik:
<security:identity
authenticate-method="#{authenticator.authenticate}"
remember-me="true"/>
Ponieważ uwierzytelnienie nie będzie się już odbywać poprzez komponent Authenticator, należy usunąć atrybut authenticate-method. Dodatkowo trzeba dopisać linijkę konfigurującą klasę encji użytkownika (user-class) i roli (role-class) dla komponentu JpaIdentityStore:
<security:identity remember-me="true"/> <security:jpa-identity-store user-class="pl.info.czerwinski.logintest.User" role-class="pl.info.czerwinski.logintest.Role"/>
Teraz już można (ale nie trzeba) usunąć komponent Authenticator i sprawdzić działanie aplikacji. Jednak polecam wpierw dodać do bazy danych jakiegoś użytkownika i przypisać mu jakąś rolę (najlepiej admin).
Podsumowanie
Opisany powyżej przykład działa, ale zakłada, że hasła w bazie nie są szyfrowane. Mimo iż zmiana w kodzie potrzebna do generowania skrótu hasła jest niewielka (wystarczy zmienić wartość atrybutu hash adnotacji @UserPassword na sha lub md5), to pierwsze logowanie do takiego systemu jest bardzo trudne…
Na razie pozostawię tę tajemnicę niewyjaśnioną, a następnym razem zajmę się właśnie tym tematem.



