piątek, 9 stycznia 2009

Kaskady pól Encji

Witam.
Każdy z nas pisząc encje z wykorzystaniem adnotacji, używał do określania relacji między nimi @OneToMany, @OneToOne i ustawiał kaskadę (jak w większości przykładów) cascade={CascadeType.ALL} (czyli przykładowo @OneToMany( cascade={CascadeType.ALL} ). Powoduje ona "przenoszenie" komend z obiektu, na którym wywołaliśmy polecenia EntityManger-a (persist, merge, remove) na zawartość pól kaskadowanych. Wszystko jest dobrze dopóki aplikacje i powiązania między encjami są proste. Jeżeli mamy na przykład sklepik z encjami Osoba i Bilet i ustawimy pełną kaskadowość. To przy usuwaniu osoby usuniemy też wszystkie bilety tej osoby. W tym wypadku wystarczy wyłączyć kaskadowość, ale w większych projektach może to być za mało.
Zobaczmy więc co za typy kaskadowania kryją się pod CascadeType.ALL.
Najważniejsze to:
  • CascadeType.PERSIST

  • CascadeType.MERGE

  • CascadeType.REMOVE

Pełna lista pod http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/#entity-hibspec-cascade

Tak Więc jeśli chcemy by wraz z stworzeniem(w bazie) jakiegoś obiektu wiązało się z stworzeniem wszystkich jego pól "encyjnych" ustawiamy CascadeType.PERSIST gdy chcemy by update lub pobranie elementu powodowało update wszystkich pól, które są związane z encjami ustawiamy CascadeType.MERGE. Gdy chcemy ustawić kilka typów kaskadowania naraz wpisujemy je po przecinku. Przykładowo @OneToMany( cascade={CascadeType.MERGE, CascadeType.PERSIST})

W razie problemów z adnotacjami polecam wcześniej już podlinkowany poradnik do Hibernate-a http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/. Będzie również pomocny przy korzystaniu z OpenJPA.
To tyle co miałem do powiedzenia jeżeli idzie o kaskadowanie.
Pytania (lub propozycje co do artykułów) pisać w komentarzach.
Pozdrawiam

poniedziałek, 5 stycznia 2009

Zabezpieczenie strony przy użyciu JAAS

Odpowiednie zabezpieczenie strony jest bardzo ważnym aspektem pisania projektu, nie chcemy przecież, aby przypadkowe osoby miały dostęp do funkcji zarządzających naszą strona/baza danych. W celu uwierzytelnienia i autoryzacji użytkowników posłużymy się narzędziem Java Authentication and Authorization Service, w skrócie JAAS.
Zacznijmy od zdefiniowania połączenia z naszą bazą, to co opisywał Dominik w swoim poście. Mój plik mysql-ds.xml wygląda następująco:

<datasources>
<local-tx-datasource>
<jndi-name>myBase</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/sklep</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password></password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>

Następnie w pliku <nasz_jboss>/server/default/conf/login-config.xml dodajemy wpis:

<application-policy name="sklepDomain">
<authentication>
<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
<module-option name="dsJndiName">java:/myBase</module-option>
<module-option name="principalsQuery">select password from osoba where login=?</module-option>
<module-option name="rolesQuery">select rola,'Roles' from role where login=? </module-option>
<module-option name="debug">true</module-option>
</login-module>
</authentication>
</application-policy>

Widzimy w tym pliku zapytania do bazy danych, tak więc przydałoby się teraz zdefiniować odpowiednie encje. W moim przypadku mamy 2 tabele, pierwsza przechowuje login oraz hasło usera, a druga login i role tego usera. Tak wiec encja Osoba musi zawierać pola tekstowe: login oraz password, a encja Role pola: login oraz rola.
Przechodzimy teraz do warstwy web'owej naszej aplikacji. W folderze WEB-INF tworzymy plik jboss-web.xml i wpisujemy:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<security-domain>java:/jaas/sklepDomain</security-domain>
</jboss-web>

Przy czym "sklepDomain" pochodzi z pliku login-config.xml
Teraz musimy zdefiniować które obszary naszej strony będą chronione oraz kto będzie miał do nich dostęp, a także gdzie znajdują się strony do logowania oraz w przypadku błędu logowania strona z błędem, trzeba także poinformować jakie role mamy zdefiniowane w naszej aplikacji. W tym celu w pliku web.xml dodajemy:

<security-constraint>
<web-resource-collection>
<web-resource-name>WRC1</web-resource-name>
<url-pattern>/faces/protected/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>

<login-config>
<auth-method>FORM</auth-method>
<realm-name>sklepDomain</realm-name>
<form-login-config>
<form-login-page>/login.html</form-login-page>
<form-error-page>/loginError.html</form-error-page>
</form-login-config>
</login-config>

<security-role>
<role-name>admin</role-name>
</security-role>

Nie pozostaje nam nic innego jak napisać formularz logowania:

<html>
<body>
<form action="j_security_check" method="post">
<input type="text" name="j_username" /><br />
<input type="password" name="j_password" /><br />
<input type="submit" value="login" />
</form>
</body>
</html>

I to wszystko :) Teraz próbując wejść na którąkolwiek ze stron z folderu protected zostaniemy przeniesieni na stronę logowania, tylko osoba z rola admin będzie mogła tam wejść.

update:
Razem z Dominikiem wykminiliśmy jak jak obsługiwać hasło haszowane algorytmem md5.
Dodajemy do pliku login-config.xml:

<module-option name="hashAlgorithm">MD5</module-option>
<module-option name="hashEncoding">base64</module-option>

Zmodyfikujemy także nieco funkcje podaną w poprzednim poście, która haszowala nasze hasło.
Teraz będzie wyglądać następująco:

private String makeMD5(String pass) throws NoSuchAlgorithmException
{
MessageDigest md = null;
String passwordHash = new String("");

try {
md = MessageDigest.getInstance("MD5");
byte[] passwordBytes = pass.getBytes();
byte[] hash = md.digest(passwordBytes);
passwordHash = Base64Encoder.encode(hash);
}catch (Exception e) {
e.printStackTrace();
}
return passwordHash;
}

sobota, 3 stycznia 2009

Wysyłanie maili z pomocą Gmaila

Dzisiaj zajmiemy się wysyłaniem maili przy użyciu konta na Gmailu. Stworzymy prostą aplikację JSF, mogącą pomóc w procesie rejestracji użytkownika i potwierdzenia poprawności wpisanego przez niego adresu e-mail.
Zacznijmy od napisania klasy obsługującej wysyłanie maili:
package pl.javazpiwnicy;

import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class MailManager
{
String addressFrom = "<nasze_konto>@gmail.com";
String temat = "Rejestracja sie powiodla!";

public void wyslij(String login, String haslo, String email) throws AddressException, MessagingException
{
String tresc = "Twoj login: " + login + "\n" + "Twoje haslo: " + haslo;
Properties props = System.getProperties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.port", "465");
props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.fallback", "false");

Authenticator auth = new SMTPAuthenticator();
Session sesja = Session.getInstance(props, auth);

MimeMessage wiadomosc = new MimeMessage(sesja);
wiadomosc.setFrom(new InternetAddress("<nasze_konto>@gmail.com"));
wiadomosc.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
wiadomosc.setSubject(temat);
wiadomosc.setText(tresc);

Transport.send(wiadomosc);
}

private class SMTPAuthenticator extends javax.mail.Authenticator
{
public PasswordAuthentication getPasswordAuthentication()
{
try
{
String username = "<nasz_login_na_gmaila>";
String password = "<nasze_haslo>";
return new PasswordAuthentication(username, password);
}catch (Exception e) {
}
return null;
}
}
}
Następnie napiszemy beana do zarządzania użytkownikiem:
package pl.javazpiwnicy;

import java.util.Random;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;

public class User
{
private String login;
private String email;
private String haslo;

private MailManager mm = new MailManager();

public String getLogin()
{
return login;
}
public void setLogin(String login)
{
this.login = login;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
private void setHaslo()
{
Random r = new Random();
haslo = Long.toString(Math.abs(r.nextLong()), 27).substring(0, 8);
}

public String rejestruj() throws AddressException, MessagingException
{
setHaslo();
mm.wyslij(login, haslo, email);
return "success";
}
}
Teraz przyszła kolej na dodatkowy wpis w faces-config.xml :
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>pl.javazpiwnicy.User</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Na koniec stworzymy bardzo prosty formularz, zawierający dwa pola do wpisania: login i adres maila:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>

</head>
<body>
<f:view>
<h:form>
<h:panelGrid columns="2">
<h:outputText value="Login"/><h:inputText value="#{user.login}"/>
<h:outputText value="E-mail"/><h:inputText value="#{user.email}"/>
<h:commandButton action="#{user.rejestruj}" value="Rejestruj"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
Klikniecie przycisku rejestruj powinno spowodować wysłanie losowo wygenerowanego hasła na maila użytkownika. W ten sposób także łatwo potwierdzimy autentyczność adresu e-mail. Kod wydaje się w miarę łatwy, więc nie będę go wyjaśniał, jakby były pytania to proszę pisać w komentarzach. A oto jak wygląda struktura katalogów tego projektu u mnie:



Na koniec dodam jeszcze prostą funkcję na szyfrowanie hasła algorytmem md5, przyda się kiedy będziecie wygenerowane hasło chcieli wrzucić do bazy:
private String makeMD5(String password) throws NoSuchAlgorithmException
{
MessageDigest m = MessageDigest.getInstance( "MD5" );
m.update( password.getBytes(), 0, password.length() );
return new BigInteger( 1, m.digest() ).toString( 16 );
}