Construcció d'Aplicacions de Programari Lliure en J2EE/Accés a bases de dades amb JDBC i seguretat amb SSL
JDBC
modificaQuè és JDBC?
modificaJDBC é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:
- Bridging drivers
- Native API Partly Java Drivers
- Net Protocol All-Java Drivers
- Native-Protocol All-Java Drivers
Ús de JDBC
modificaCàrrega del controlador
modificaPer 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ó
modificaUn 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
modificaAra què disposem de l'objecte Connection podem utilitzar comandes SQL. JDBC ens proporciona tres tipus d'objecte per aconseguir aquest proposit.
- Statement: Ens permet fer una consulta o manipulació de dades (SELECT, CREATE o UPDATE)
- PreparedStatement: Ens permet precompilar la comanda per a oferir millors prestacions
- 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
modificaL'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:
- first: Ens porta a la primera fila
- absolute(número_de_fila): Ens porta a una fila específica.
- 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
modificaLes 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
modificaSi 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
modificaConfigurarem el tomcat perquè aquest sigui capaç de realitzar una connexio a una BD MYSQL.
- Afegirem el driver de MYSQL dins del directori common/lib del Tomcat
- Editarem el fitxer server.xml (Ho farem sobre una de les configuracions de servidor dins de Eclipse)
- 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- Utilitzarem l'eclipsi per a crear un nou projecte web (Dynamic Web Project).
- Crearem un nou servlet el qual tindrà els mètodes init, doGet i doPost
- Crearem un objecte DataSource
- En el mètode ini solicitarem el DataSource a partir del contexte configurat al tomcat.
- En el metode doPost obtindrem una connexió a partir de l'objecte del tipusDataSource
- 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
modificaEns 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
modificaLes 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
modificaSSL (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
modificaAquesta 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.
- Obrirem el fitxer /etc/httpd/cond.d/ssl.conf (Aquest fitxer descriu el host virtual protegit amb SSL)
- 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
- 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
modificaCrear un certificat RSA per a Tomcat
modificaPer 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
modificaFalta 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