Maj
09
2009

Proste uwierzytelnienie z JBoss Seam

No Gravatar

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

Napisz Komentarz

*