Construcció d'Aplicacions de Programari Lliure en J2EE/Accés a bases de dades amb JDBC i seguretat amb SSL

JDBC[modifica]

Què és JDBC?[modifica]

JDBC és un conjunt de métodes i interficies que ens permeten accedir i manipular bases de dades. Gràcies a ells podem utilitzar codi SQL dins del codi Java. Podeu visitar la web oficial o mirar el tutorial

JDBC està pensat per a no tenir que patir sobre el funcionament del SGBD que volem utilitzar, ja que per a nosaltres, tot el procés de comunicació ens serà transparent. L'encarregat de entendres amb el SGBD serà el controlador (driver) específic de cada JDBC.

Existeixen 4 tipus de controladors:

  1. Bridging drivers
  2. Native API Partly Java Drivers
  3. Net Protocol All-Java Drivers
  4. Native-Protocol All-Java Drivers

Ús de JDBC[modifica]

Càrrega del controlador[modifica]

Per poder utilitzar el controlador del SGBD que hem escollit, primer hem de carregar el controlador. Per fer-ho s'utilitza el següent codi:

try{
    class.forName("org.postgresql.Driver");
}
catch(ClassNotFoundException ex){
    //Codi de l'excepció
}

El mètode forname carrega el controlador que correspon a la cadena que passem per paràmetres. En aquest cas estariam carregant el controlador del SGBD postgresql.

Els noms del controladors dels diferents SGBD els podeu trobar en el següent enllaç: Noms controladors JDBC

Establir connexió[modifica]

Un cop carregat el controlador podem establir una connexió. Per poder-ho fer haurem de saber la URL (Universal Resource Locator).

La forma de definir la URL varia depenent del SGBD. Podeu veure diferents exemples en el següent enllaç: Formats de URL per a connexions JDBC

Per obtenir la connexió farem us del mètode DriveMangaer.getConnection(). Aquest mètode ens retornara un objecte del tipus Connection que conté una connexió establerta amb la BD. Podem demanar la connexió especificant diferent nombre de paràmentres, però el més habitual és fer-ho així:

Connection connexio = DriverManager.getConnection("URL","nomUsuari","clau d'accés");

Per poder veure les diferents variants a l'hora de obtindre connexions podeu consultar l'API

Quan la connexió ja no sigui necessària hem de utilitzar el mètode close de l'objecte Connectionper alliberar-la.

connexio.close()

Accés a la BD[modifica]

Ara què disposem de l'objecte Connection podem utilitzar comandes SQL. JDBC ens proporciona tres tipus d'objecte per aconseguir aquest proposit.

  1. Statement: Ens permet fer una consulta o manipulació de dades (SELECT, CREATE o UPDATE)
  2. PreparedStatement: Ens permet precompilar la comanda per a oferir millors prestacions
  3. CallableStatement: Ens permet cridar a procediments definits en la BD.

Per a obtenir aquests objectes ho farem desde l'objecte del tipus Connection

Statement stmt = connexio.createStatement();

Un cop creat el podem utilitzar per a executar commandes SQL dins la BD. Per fer consultes utilitzarem el mètode executeQuery

ResultsetSet rs = stmt.executeQuery("SELECT * FROM TAULA");

El paràmetre d'aquest mètode és una cadena que representa la consulta SQL. El resultat el recollim dins d'un objecte ResultSet. Aquest tipus d'objecte el veurem en més detall en el següent apartat.

Disposem d'un altre mètode per a executar commandes que no retornen cap resultat (DELETE, UPDATE, ...).

int nombreFilesAfectades = stmt.executeUpdate("DELETE FROM TAULA1 WHERE id like 'id-100%');

El mètode executeUpdate ens retorna el nombre de files afectades per la commanda que li passem per paràmetre.

Si a priori no sabem si la nostra comanda ens ha de retornar un resultat, podem utilitzar el mètode execute que ens retorna true si aquest té resultat i false en altre cas. Per recollir el valor o el nombre de línies afectades ho farem amb els mètodes getResultSet() i getUpdateCount() respectivament.

En el cas de que necessitem executar el mateixa commanda amb diferents valors podem fer ús del preparedStatement. Aquest també l'obtenim de l'objecte Connection

PreparedStatement preStmt = connexio.prepareStatement("UPDATE TAULA1 SET CAMP1 = ? WHERE ID = ?");

En aquest cas el mètode que ens proporciona el PreparedStatement té un pàrametre que és la commanda SQL. Aquesta commanda es enviada al controlador de la BD i precompilada. Ara hem assignar valor a aquells llocs on hem situat el caràcter ?. Per fer-ho utilitzarem els mètodes que té el PreparedStatement per aquesta finalitat. Són del tipus setXXX(valor). Per a saber amb quins mètodes compta pots visitar l'API.


preStmt.setInt(12);
preStmt.setString("id-1000");

Ara resta executar la commanda:

int nombreFilesAfectades = preStmt.executeUpdate();

Per a cridar procediments ho farem mitjançant l'objecte CallableStatement. Aquest objecte conté una crida a un procediment definit a la BD com per exemple el següent:

create procedure MOSTRA_TAULA as
SELECT 
       t1.CAMP1, 
       t2.CAMP2, 
       t2.CAMP3 cmp2 
FROM 
      TAULA1 t1, 
      TAULA2 t2 
WHERE 
      t1.CAMP1 = t2.CAMP2 and 
      t1.CAMP1='clau' 
ORDER BY 
      t2.CAMP3

La sintaxis del procediment canvia depenent del SGBD, alguns requereixen anar entre begin i end, etc. Per crear el CallableStatement també ho farem a partir de l'objecte del tipusConnection. Un cop crean només hem d'executar la commanda mitjançant els mètodes que vists amb anterioritat.

CallableStatement callStmt = con.prepareCall("{call MOSTRA_TAULA}");
ResultSet rs = callStmt.executeQuery();

Quan ja no necessitem els objectes de sentencia els hem de tancat mitjançant el mètode close

stmt.close();

Resultats[modifica]

L'execució d'una commanda SQL ens retorna una taula accessible mitjançant un objecte del tipus ResultSet. Aquest objecte ens propociona un conjunt de mètodes i propietats per a llegir les dades. El següent codi permet veure com accedim a les dades de dins del ResultSet:


Statement stmt = connexio.createStatement();
ResultsetSet resultSet = stmt.executeQuery("SELECT * FROM TAULA");

while (resultSet.next()){
    System.out.println("El id és: "+ resultSet.getString("ID"));
    System.out.println("El valor del camp1 és: "+ resultSet.getInt("CAMP1"));
    System.out.println("El valor del camp2 és: "+ resultSet.getString("CAMP2");
}
resultSet.close();
stmt.close();

El mètode next() accedeix una a una a les fileres que contenen les dades. Els metodes getXXX("nom columna") ens retornen el valor de la columna especificada per paràmetre en la fila actual. Per veure els diferents tipus de retorn podeu consultar l'API.

L'accés a les dades només es fa en una direcció. Per tant hem de processar les dades segons anem recorrent la taula. No és fins a la versió 2.0 de JDBC que ens permet despaçar-nos lliurement per tots els resultats. Gràcies a aquestes millores comptem amb mètodes com:

  1. first: Ens porta a la primera fila
  2. absolute(número_de_fila): Ens porta a una fila específica.
  3. previous: Ens porta a la fila anterior.

...

És en aquesta versió on apareix també la possibilitat de actualitzar o inserir noves files.

  resultSet.absolute(100); //Ens posicionem a la fila nº100
  resultSet.updateInt("CAMP1", 2); // Assigna a camp1 amb el valor sencer 2
  resultSet.updateString("CAMP2","NOUVALOR"); //Assigna  a camp2 amb el valor NOUVALOR
  resultSet.updateRow(); // Actualitza amb els nous valors
   resultSet.moveToInsertRow(); // Ens posiciona a una nova fila
   resultSet.updateString("ID","id-100000"); //Assigna a ID el valor id-100000
   resultSet.updateInt("CAMP1", 20); // Assigna a camp1 amb el valor sencer 20
   resultSet.updateString("CAMP2","NOUVALOR"); //Assigna  a camp2 amb el valor NOUVALOR
   resultSet.insertRow(); //Afegeix la fila
   resultSet.moveToCurrentRow(); //Ens desplaça a la fila actual

Per defecte el ResultSet que ens retorna l'Statement no ens permet fer aquestes operacions. Aquestes habilitats les haurem d'especificar a l'hora de crear l'Statement.

Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                                      ResultSet.CONCUR_UPDATABLE);

Accés a les metadades[modifica]

Les metadades no són res més que informació referent al SGBD. Per obtenir aquestes dades tenim l'objecte anomenat DataBaseMetaData. Aquest objecte el podem recollir a partir de l'objecte Connection

DataBaseMetaData dbmd = connexio.getMetaData();

Per a veure la informació que podeu obtenir mitjançant el BaseDataMetaData podeu consultar lAPI

A més a més de obtenir informació relativa al SGBD podem obtenir informació sobre els resultats d'una consulta. Aquestes dades les obtenim mitjançant l'objecte ResultSetMetaData

ResultSet resultSet = stmt.executeQuery(SELECT * from TAULA1);
ResultSetMetaData rsmd = resultSet.getMetaData();
if (rsmd.isReadOnly){
  System.out.println("El ResultSet no és modificable");
}

Per a veure la informació que podeu obtenir mitjançant el ResultSetMetaData podeu consultar lAPI


Tornar a la plana principal: Construcció d'Aplicacions de Programari Lliure en J2EE

Connexions sobre Tomcat[modifica]

Si pretem utilitzar JDBC en una aplicació web podem utilitzar les capacitats de tomcat per poder gestionar connexions. Per fer-ho utilitzarem JNDI DataSource.

En el JNDI Datasource HOW-TO podem veure en profunditat com funciona aquest sistema.

Seguirem el següent exemple:

Configuració del tomcat[modifica]

Configurarem el tomcat perquè aquest sigui capaç de realitzar una connexio a una BD MYSQL.

  1. Afegirem el driver de MYSQL dins del directori common/lib del Tomcat
  2. Editarem el fitxer server.xml (Ho farem sobre una de les configuracions de servidor dins de Eclipse)
  3. Afegirem un nou contexte aquest ha d'anar dins del node <HOST>
<Context path="/exempleJDBCdataSource" docBase="exempleJDBCdataSource"
        debug="5" reloadable="true" crossContext="true">

    <!-- maxActive: Maximum number of dB connections in pool. Make sure you
         configure your mysqld max_connections large enough to handle
         all of your db connections. Set to 0 for no limit.
         -->

    <!-- maxIdle: Maximum number of idle dB connections to retain in pool.
         Set to -1 for no limit.  See also the DBCP documentation on this
         and the minEvictableIdleTimeMillis configuration parameter.
         -->

    <!-- maxWait: Maximum time to wait for a dB connection to become available
         in ms, in this example 10 seconds. An Exception is thrown if
         this timeout is exceeded.  Set to -1 to wait indefinitely.
         -->

    <!-- username and password: MySQL dB username and password for dB connections  -->

    <!-- driverClassName: Class name for the old mm.mysql JDBC driver is
         org.gjt.mm.mysql.Driver - we recommend using Connector/J though.
         Class name for the official MySQL Connector/J driver is com.mysql.jdbc.Driver.
         -->
    
    <!-- url: The JDBC connection url for connecting to your MySQL dB.
         The autoReconnect=true argument to the url makes sure that the
         mm.mysql JDBC Driver will automatically reconnect if mysqld closed the
         connection.  mysqld by default closes idle connections after 8 hours.
         -->

 	 <Resource name="jdbc/provaConnexio" auth="Container" type="javax.sql.DataSource"
               maxActive="100" maxIdle="30" maxWait="10000"
               username="estiu" password="estiu" driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/airline"/>

	</Context>

Creació d'una aplicació web[modifica]

  1. Utilitzarem l'eclipsi per a crear un nou projecte web (Dynamic Web Project).
  2. Crearem un nou servlet el qual tindrà els mètodes init, doGet i doPost
  3. Crearem un objecte DataSource
  4. En el mètode ini solicitarem el DataSource a partir del contexte configurat al tomcat.
  5. En el metode doPost obtindrem una connexió a partir de l'objecte del tipusDataSource
  6. Llegirem dades de la BD
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import javax.sql.DataSource;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * Servlet implementation class for Servlet: provaConnexio
 *
 */
 public class provaConnexio extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
    /* (non-Java-doc)
	 * @see javax.servlet.http.HttpServlet#HttpServlet()
	 */
	 
	private DataSource origenDades = null;
	
	public provaConnexio() {
		super();
	}   	 	

	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request,response);
	}
	
	/* (non-Java-doc)
	 * @see javax.servlet.http.HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
		
		try{
			Connection connexio = origenDades.getConnection();
			Statement stmt = connexio.createStatement();
			
			ResultSet rs = stmt.executeQuery("Select * from bitllet");
			
			while (rs.next()){
				out.println("El bitllet " + rs.getString("id") + " es per al vol " + rs.getString("idVol"));
			}
			
			
		}catch(SQLException ex){
			ex.printStackTrace();
		}
		
	}   	  	  
	
	/* (non-Javadoc)
	 * @see javax.servlet.GenericServlet#init()
	 */
	public void init() throws ServletException {
		// TODO Auto-generated method stub
		super.init();
        try
        {
            // recuperamos el contexto inicial y la referencia a la fuente de datos
        	System.out.println("Executo l'init");
            Context ctx = new InitialContext();
            origenDades = (DataSource) ctx.lookup("java:comp/env/jdbc/provaConnexio");
        }
        catch (Exception e)
        {
            throw new ServletException("Imposible recuperar java:comp/env/jdbc/tutoriales",e);
        }
		
		
	}   
}

Configuració de l'aplicació web[modifica]

Ens resta configurar el fitxer web.xml de l'aplicació. Inclourem el següent codi dins del node <webapp> del .

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<display-name>
	exempleJDBCdataSource</display-name>
	
	<resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/provaConnexio</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
  </resource-ref>
	<servlet>
		<description>
		</description>
		<display-name>
		provaConnexio</display-name>
		<servlet-name>provaConnexio</servlet-name>
		<servlet-class>
		provaConnexio</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>provaConnexio</servlet-name>
		<url-pattern>/provaConnexio</url-pattern>
	</servlet-mapping>
	
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>
</web-app>

Ara la nostra connexió prodrà fer ús d'aquest recurs.

Seguretat[modifica]

Les dades que circulen desde el servidor (Aplicació web) al client (Navegador) poden ser interceptades per una tercera persona si aquest té accés als paquets HTTP que circulen per la xarxa. Aquests paquets poden ser llegits i en poden extreure la informació.

En molts cops hem de fer circular dades secretes (claus d'accés, dades personals, etc). Per aquest motiu és necessari tenir un protocol per poder a poder fer connexions segures. Aquest és HTTPS (HTTP + SSL).


SSL[modifica]

SSL (Secure Socket Layer) és un sistema de xifratge dissenyat per l'empresa Netscape Communications, que permet xifrar connexions. Es basa en un sistema criptogràfic asimètric i en el concepte dels certificats.

Per a més informació polseu aquí

SSL mitjançant Apache[modifica]

Aquesta configuració protegirà les aplicacions web mitjançant connexions SSL. El primer pas serà configurar l'apache perquè redireccioni les peticions HTTPS del port 443 cap a el nostre port 8080 que és on tenim l'aplicació web.

Per defecte l'apache ja ve preparat i configurat per suportar SSL.

Per fer la unió utilitzarem els paràmetres ProxyPass i el ProxyPassReverse.

  1. Obrirem el fitxer /etc/httpd/cond.d/ssl.conf (Aquest fitxer descriu el host virtual protegit amb SSL)
  2. Inserirem les següents línies dins de l'àmbit del virtualhost
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8008
  1. Engegarem el apache /etc/init.d/httpd start

Amb això consguim que totes les peticions realitzades al port 443 les redireccioni al 8080.

Els certificats que utilitza Apache són els que venen per defecte en la instal·lació. Però modificant les línies d'aquest fitxer ho configuraríem perquè utilitzés els certificats propis.

Tomcat amb SSL[modifica]

Crear un certificat RSA per a Tomcat[modifica]

Per provar SSL en Tomcat necessitarem un certificat. El procediment normal seria generar una petició de certificat perquè una Autoritat de certificació ens signés la nostra clau pública i així poder demostrar que som qui diem que som.

El que farem nosaltres serà crear un certificat autosignat. És a dir no fem us dels serveis de cap (CA) per a que ens signi el certificat.

keytool crea parells de claus públiques y privadas, certificats autosignats, i gestiona magatzems de claus

keytool -genkey -alias tomcat -keyalg RSA 

Aquesta comanda ens generarà un certificat autosignat per a que Tomcat el pugui fer servir. Ens demanarà un password, que per defecte es changeit i aleshores un serie de paràmetres del certificat.

Enter keystore password:  changeit
What is your first and last name?
  [Unknown]:  alex
What is the name of your organizational unit?
  [Unknown]:  Universitat d'estiu
What is the name of your organization?
  [Unknown]:  UdL
What is the name of your City or Locality?
  [Unknown]:  Lleida
What is the name of your State or Province?
  [Unknown]:  lleida
What is the two-letter country code for this unit?
  [Unknown]:  ES
Is CN="alex ", OU=Universitat d'estiu, O=UdL, L=Lleida, ST=lleida, C=ES correct?
  [no]:  yes

Enter key password for <tomcat>
        (RETURN if same as keystore password):

Aquest queda emmagatzemat en el fitxer .keystore dins del nostre $HOME En el cas d'haver assignat un password diferent a l'hora entrar les dades del certificat, l'haurem d'especificar quan configurem el TOMCAT.


També és possible importar certificats generats amb OPENSSL:

Per a generar una petició i una nova clau.

openssl req -new -out REQ.pem -keyout KEY.pem

Per a generar un certificat x509 auto-signat desde una peticó de certidicat utilitzant la clau.

openssl req -x509 -in REQ.pem -key KEY.pem -out	CERT.pem

Importa el certificat al keystore

keytool -import -v -trustcacerts -alias tomcat -file CERT.pem

Configuració del Tomcat[modifica]

Falta dir-li a Tomcat que utilitzi SSL. Hem de configurar el fitxer PATH al TOMCAT/conf/server.xml

Aquí hem d'habilitar el següent connector

 <Connector port="8443" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" disableUploadTimeout="true"
               acceptCount="100" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" />

En el cas d'haver canviat el password del keystore afegiriam el paràmetre keystorePass. Si el que hem canviat és la ubicació de del fitxer keystore ho especificarem mitjançant el paràmetre keystoreFile Ex:

<Connector port="8443" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" disableUploadTimeout="true"
               acceptCount="100" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS"
               keystorePass="noupasswd" keystoreFile="/usr/local/keystores/keystore1"/>

Per aprofundir en la qüestió podeu visitar la HOW-TO de Tomcat que parla de la configuració amb SSL