vamos a ver cómo leer desde java una página web ofrecida por un servidor http

23
Vamos a ver cómo leer desde java una página web ofrecida por un servidor http, por un servidor https que no requiere certificado de cliente y por un servidor https que sí requiere certificado de cliente. Primero haremos aproximaciones con código java. Al final un ejemplo sin tanto rollo de código, pero usando las System Properties de java. Servidor http El código para leer una página web ofrecido por un servidor http normalito puede ser el siguiente package com.chuidiang.ejemplos; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; public class PruebaHTTP { public static void main(String[] args) { try { // Se abre la conexión URL url = new URL("http://www.chuidiang.com");

Upload: roman-sadowski

Post on 23-Nov-2015

69 views

Category:

Documents


1 download

TRANSCRIPT

Vamos a ver cmo leer desde java una pgina web ofrecida por un servidor http, por un servidor https que no requiere certificado de cliente y por un servidor https que s requiere certificado de cliente. Primero haremos aproximaciones con cdigo java. Al final un ejemplo sin tanto rollo de cdigo, pero usando las System Properties de java.

Servidor http El cdigo para leer una pgina web ofrecido por un servidor http normalito puede ser el siguiente package com.chuidiang.ejemplos;

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.MalformedURLException;import java.net.URL;import java.net.URLConnection;

public class PruebaHTTP { public static void main(String[] args) { try { // Se abre la conexin URL url = new URL("http://www.chuidiang.com"); URLConnection conexion = url.openConnection(); conexion.connect(); // Lectura InputStream is = conexion.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); char[] buffer = new char[1000]; int leido; while ((leido = br.read(buffer)) > 0) { System.out.println(new String(buffer, 0, leido)); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}El cdigo es sencillo. Hacemos un new URL() con la URL en cuestin. Llamamos al mtodo openConnection() y luego connect(). Slo nos queda obtener el InputStream de lectura y leer en l como mejor nos venga para nuestras necesidades. En el ejemplo vamos leyendo en buffer de caracteres y sacndolo por pantalla, por lo que obtendremos en pantalla el texto html de la pgina cuya URL hemos abierto (suponiendo que esa URL tenga un texto html).

Servidor https que no requiere certificado de cliente Cuando se abre una conexin con https contra un servidor, el servidor nos presenta un certificado digital. El cliente debe decidir si acepta o no ese certificado digital. Todos hemos visto esto en alguna ocasin cuando navegamos por internet. Las pginas https que usamos ofrecen un certificado digital. Si nuestro navegador confa en l, todo va de perlas. Si nuestro navegador no confa en ese certificado, nos mostrar un aviso de si estamos seguros que queremos visitar esa pgina. El cdigo para leer de un servidor https que no requiere certificado de cliente es en principio exactamente igual que el anterior salvo por dos pequeos detalles: En la URL, en vez de http://.... ponemos https://..... Debemos indicar a nuestro cdigo java en qu certificados de servidor confiamos. Si no obtendremos error. Para indicar a nuestro cdigo java los certificados en los que debe confiar, necesitamos tener esos certificados en ficheros. Cualquier navegador, visitando la pgina en cuestin, suele ofrecer en algn sitio la posibilidad de ver los detalles del certificado del servidor y guardarlos. En la barra de la URL del navegador suele aparecer un candadito cuando visitamos una pgina https. Pinchando el candadito podremos ver los datos del certificado y en la ventana que sale tenemos opcin de guardarlo en algn fichero. Una vez que tenemos el o todos los certificados necesarios, debemos guardarlos en un nico fichero de almacn de certificados. Podemos hacerlo, entre otras, con openssl o con keytool. Vamos con esta ltima puesto que viene con java. Si hemos guardador los distintos certificados en servidor1.cer, servidor2.cer, etc, el comando para crear el almacn de certificados de esta manera keytool -importcert -alias servidor1 -keystore .keystore -file servidor1.cerkeytool -importcert -alias servidor2 -keystore .keystore -file servidor2.cer...A cada certificado debemos darle un "alias" distinto para identrificarlo dentro del almacn. En el ejemplo los hemos llamado servidor1 y servidor2. Con -keystore .keystore estamos indicando que nuestro fichero de almacn ser ".keystore", podemos darle el nombre que queramos, pero usaremos el mismo nombre cada vez si queremos que los distintos certificados se vayan aadiendo a ese fichero. Finalmente, con -file vamos indicando cada uno de los certificados que nos hemos bajado con el navegador. Al ejecutar estos comandos nos pedir una password. Puede ser la que queramos, pero es importante que recordarla, ya que es la que nos permitir leer ms adelante el contenido de ese fichero, aadir ms certificados o borrar los existentes. Esa password deberemos usarla tambin en nuestro cdigo java. El cdigo java para leer el contenido de una pgina ofrecida bajo https puede ser como este package com.chuidiang.ejemplos;

import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.MalformedURLException;import java.net.URL;import java.net.URLConnection;import java.security.KeyManagementException;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.cert.CertificateException;

import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManager;import javax.net.ssl.TrustManagerFactory;import javax.net.ssl.X509TrustManager;

public class PruebaHTTPSOneWay { public static void main(String[] args) { try { // Carga del fichero que tiene los certificados de los servidores en // los que confiamos. InputStream fileCertificadosConfianza = new FileInputStream(new File( "c:/unpath/.keystore")); KeyStore ksCertificadosConfianza = KeyStore.getInstance(KeyStore .getDefaultType()); ksCertificadosConfianza.load(fileCertificadosConfianza, "unaPassword".toCharArray()); fileCertificadosConfianza.close();

// Ponemos el contenido en nuestro manager de certificados de // confianza. TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ksCertificadosConfianza);

// Creamos un contexto SSL con nuestro manager de certificados en los // que confiamos. SSLContext context = SSLContext.getInstance("TLS"); context.init(null, tmf.getTrustManagers(), null); SSLSocketFactory sslSocketFactory = context.getSocketFactory();

// Abrimos la conexin y le pasamos nuestro contexto SSL URL url = new URL("https://una.url.com"); URLConnection conexion = url.openConnection(); ((HttpsURLConnection) conexion).setSSLSocketFactory(sslSocketFactory);

// Ya podemos conectar y leer conexion.connect(); InputStream is = conexion.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); char[] buffer = new char[1000]; int leido; while ((leido = br.read(buffer)) > 0) { System.out.println(new String(buffer, 0, leido)); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (KeyStoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (CertificateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (KeyManagementException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}

En el primer bloque de cdigo leemos el fichero de almacn de certificados que generamos con keytool. Debemos indicar la password que usamos para crear ese fichero almacn. Tambin debemos indicar de qu tipo es el fichero almacn, con la lnea que dice KeyStore.getInstance(KeyStore.getDefaultType()). El defaultType, cmo no, es el formato que usa java y la herramienta keytool. Dejamos el segundo bloque para explicar despus. En el tercer bloque creamos un contexto SSL y a l tenemos que pasarle un TrutManager (en realidad un array de TrustManager). Este TrusManager no es ms que una clase a la que el socket SSL (nuestra conexin https) preguntar si un certificado ofrecido por un servidor es o no vlido. Este TrusManager se pasa como segundo parmetro en el mtodo context.init() del SSLContext. Y cmo obtenemos ese TrusManager a partir de nuestro almacn .keystore?. A travs de una fbrica de TrusManager, es decir, a partir de la clase TrustManagerFactory. Ese es nuestro segundo bloque de cdigo. Obtenemos esa fbrica con el mtodo TrustManagerFactory.getInstante() y la inicializamos con el KeyStore que cargamos en el primer paso. Ya slo nos queda preparar la conexin como hicimos antes, pasarle nuestro contexto SSL y leer de la forma habitual. Servidor https que requiere certificado de cliente El certificado de cliente es un fichero que normalmente alguien nos proporcionar para que podamos acceder a un servidor que lo requiera. Sin l no podremos acceder. Este fichero suele tener extensin .p12 y debemos cargarlo tambin en nuestro cdigo java de una forma similar a como hicimos con el almacen de certificados de servidor que admitimos. El cdigo java puede ser como este package com.chuidiang.ejemplos;

import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.MalformedURLException;import java.net.URL;import java.net.URLConnection;import java.security.KeyManagementException;import java.security.KeyStore;import java.security.KeyStoreException;import java.security.NoSuchAlgorithmException;import java.security.UnrecoverableKeyException;import java.security.cert.CertificateException;

import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.KeyManager;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManager;import javax.net.ssl.TrustManagerFactory;import javax.net.ssl.X509TrustManager;

public class PruebaHTTPSTwoWay { public static void main(String[] args) { try { // Carga del fichero que tiene los certificados de los servidores en // los que confiamos. InputStream fileCertificadosConfianza = new FileInputStream(new File( "c:/unpath/.keystore")); KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(fileCertificadosConfianza, "unaPassword".toCharArray()); fileCertificadosConfianza.close();

// Ponemos el contenido en nuestro manager de certificados de // confianza. TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ks);

// Cargamos el fichero con el certificado de cliente InputStream fileCertificadoCliente = new FileInputStream( new File( "c:/unpath/unCertificadoCliente.p12")); KeyStore ksCliente = KeyStore.getInstance("PKCS12"); ksCliente.load(fileCertificadoCliente, "unaPassword".toCharArray()); fileCertificadoCliente.close();

// Creamos un manager de certificados con nuestro certificado de // cliente KeyManagerFactory kmf = KeyManagerFactory .getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ksCliente, "unaPassword".toCharArray());

// Creamos un contexto SSL tanto con el manger de certificados de // servidor en los que confiamos como con el manager de certificados de // cliente de los que disponemos SSLContext context = SSLContext.getInstance("TLS"); context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); SSLSocketFactory sslSocketFactory = context.getSocketFactory();

// Abrimos la conexin y le pasamos el contexto SSL que hemos creado URL url = new URL("https://un.servidor.que.requiere.certificado.cliente.com"); URLConnection conexion = url.openConnection(); ((HttpsURLConnection) conexion).setSSLSocketFactory(sslSocketFactory);

// Ya podemos leer. conexion.connect(); InputStream is = conexion.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); char[] buffer = new char[1000]; int leido; while ((leido = br.read(buffer)) > 0) { System.out.println(new String(buffer, 0, leido)); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (KeyStoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (CertificateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (KeyManagementException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnrecoverableKeyException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}El cdigo es bsicamente igual al del ejemplo anterior, pero hemos aadido la carga del certificado de cliente. En un primero trozo de cdigo, cargamos en un KeyStore el certificado de cliente unCertificadoCliente.p12 Como al contexto SSL tendremos que pasarle, adems del TrustManager anterior, un KeyManager con la clave de cliente, primero obtenemos una fbrica de KeyManager (un KeyManagerFactory) al que inicializamos con el KeyStore que es el certificado de cliente. Este KeyManagerFactory nos permitir obtener el KeyManager que pasaremos, un poco ms abajo, como primer parmetro del contexto SSL. Ahora slo queda pasar el contexto SSL a la URLConnection, abrirla y leer de la forma habitual. Un detalle nada ms. Un certificado de cliente p12 est en un formato PKCS12, por eso en el KeyStore usamos un getInstance("PKCS12") en vez de un getDefaultType(), que corresponde a "JKS" que es el formato por defecto que da keytool a los almacenes de certificados. Tambin tenemos que pasar la clave que habitualmente protege estos certificados.

https usando java system properties Podemos hacer este ltimo ejemplo sin necesidad de tanto cdigo. Nos bastara con configurar todo usando las System Properties de java. El cdigo quedara tan sencillo como esto package com.chuidiang.ejemplos;

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.MalformedURLException;import java.net.URL;import java.net.URLConnection;

public class PruebaHTTPSTwoWayProperties { public static void main(String[] args) { try { System.setProperty("javax.net.ssl.trustStore", "c:/unpat/.keystore"); System.setProperty("javax.net.ssl.trustStorePassword", "unaPassword"); System.setProperty("javax.net.ssl.trustStoreType", "JKS");

System.setProperty("javax.net.ssl.keyStore", "c:/unpath/unCertificadoCliente.p12"); System.setProperty("javax.net.ssl.keyStorePassword", "unaPassword"); System.setProperty("javax.net.ssl.keyStoreType", "PKCS12");

// Abrimos la conexin y le pasamos el contexto SSL que hemos creado URL url = new URL("https://un.servidor.que.requiere.certificado.cliente.com"); URLConnection conexion = url.openConnection();

// Ya podemos leer. conexion.connect(); InputStream is = conexion.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); char[] buffer = new char[1000]; int leido; while ((leido = br.read(buffer)) > 0) { System.out.println(new String(buffer, 0, leido)); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}La diferencia es que estas propiedades afectaran a todo nuestro programa en java. Puede ser efectivo si todas nuestras conexiones van a usar y admitir los mismos certificados. Sin embargo si queremos que nuestro programa abra diferentes conexiones usando/admitiendo diferentes conjuntos de certificados, debemos hacerlo con cdigo, ya que ah podemos pasar un SSLContext especfico a cada conexin que abramos.

JavaBeans Component Design Conventions JavaBeans component design conventions govern the properties of the class and govern the public methods that give access to the properties. A JavaBeans component property can be Read/write, read-only, or write-only Simple, which means it contains a single value, or indexed, which means it represents an array of valuesThere is no requirement that a property be implemented by an instance variable; the property must simply be accessible using public methods that conform to certain conventions: For each readable property, the bean must have a method of the formPropertyClass getProperty() { ... } For each writable property, the bean must have a method of the formsetProperty(PropertyClass pc) { ... } In addition to the property methods, a JavaBeans component must define a constructor that takes no parameters. The Duke's Bookstore application JSP pages enter.jsp, bookdetails.jsp, catalog.jsp, and showcart.jsp use the database.BookDB and database.BookDetails JavaBeans components. BookDB provides a JavaBeans component front end to the access object BookDBAO. Both beans are used extensively by bean-oriented custom tags (see Custom Tags in JSP Pages). The JSP pages showcart.jsp and cashier.jsp use cart.ShoppingCart to represent a user's shopping cart. The JSP pages catalog.jsp, showcart.jsp, and cashier.jsp use the util.Currency JavaBeans component to format currency in a locale-sensitive manner. The bean has two writable properties, locale and amount, and one readable property, format. The format property does not correspond to any instance variable, but returns a function of the locale and amount properties. public class Currency {private Locale locale;private double amount;public Currency() {locale = null;amount = 0.0;}public void setLocale(Locale l) {locale = l;}public void setAmount(double a) {amount = a;}public String getFormat() {NumberFormat nf =NumberFormat.getCurrencyInstance(locale);return nf.format(amount);}} Why Use a JavaBeans Component? A JSP page can create and use any type of Java programming language object within a declaration or scriptlet. The following scriptlet creates the bookstore shopping cart and stores it as a session attribute: If the shopping cart object conforms to JavaBeans conventions, JSP pages can use JSP elements to create and access the object. For example, the Duke's Bookstore pages bookdetails.jsp, catalog.jsp, and showcart.jsp replace the scriptlet with the much more concise JSP useBean element: Creating and Using a JavaBeans Component You declare that your JSP page will use a JavaBeans component using either one of the following formats: or

The second format is used when you want to include jsp:setProperty statements, described in the next section, for initializing bean properties. The jsp:useBean element declares that the page will use a bean that is stored within and accessible from the specified scope, which can be application, session, request, or page. If no such bean exists, the statement creates the bean and stores it as an attribute of the scope object (see Using Scope Objects). The value of the id attribute determines the name of the bean in the scope and the identifier used to reference the bean in other JSP elements and scriptlets.

Note: In JSP Scripting Elements, we mentioned that you must import any classes and packages used by a JSP page. This rule is slightly altered if the class is only referenced by useBean elements. In these cases, you must only import the class if the class is in the unnamed package. For example, in What Is a JSP Page?, the page index.jsp imports the MyLocales class. However, in the Duke's Bookstore example, all classes are contained in packages and thus are not explicitly imported.

The following element creates an instance of Currency if none exists, stores it as an attribute of the session object, and makes the bean available throughout the session by the identifier currency: Setting JavaBeans Component Properties There are two ways to set JavaBeans component properties in a JSP page: with the jsp:setProperty element or with a scriptlet The syntax of the jsp:setProperty element depends on the source of the property value. Table 4-3 summarizes the various ways to set a property of a JavaBeans component using the jsp:setProperty element.Table 4-3 Setting JavaBeans Component Properties

Value Source Element Syntax

String constant

Request parameter

Request parameter name matches bean property

Expression

1. beanName must be the same as that specified for the id attribute in a useBean element. 2. There must be a setPropName method in the JavaBeans component. 3. paramName must be a request parameter name.

A property set from a constant string or request parameter must have a type listed in Table 4-4. Since both a constant and request parameter are strings, the Web container automatically converts the value to the property's type; the conversion applied is shown in the table. String values can be used to assign values to a property that has a PropertyEditor class. When that is the case, the setAsText(String) method is used. A conversion failure arises if the method throws an IllegalArgumentException. The value assigned to an indexed property must be an array, and the rules just described apply to the elements. Table 4-4 Valid Value Assignments

Property Type Conversion on String Value

Bean Property Uses setAsText(string-literal)

boolean or Boolean As indicated in java.lang.Boolean.valueOf(String)

byte or Byte As indicated in java.lang.Byte.valueOf(String)

char or Character As indicated in java.lang.String.charAt(0)

double or Double As indicated in java.lang.Double.valueOf(String)

int or Integer As indicated in java.lang.Integer.valueOf(String)

float or Float As indicated in java.lang.Float.valueOf(String)

long or Long As indicated in java.lang.Long.valueOf(String)

short or Short As indicated in java.lang.Short.valueOf(String)

Object new String(string-literal)

You would use a runtime expression to set the value of a property whose type is a compound Java programming language type. Recall from Expressions that a JSP expression is used to insert the value of a scripting language expression, converted into a String, into the stream returned to the client. When used within a setProperty element, an expression simply returns its value; no automatic conversion is performed. As a consequence, the type returned from an expression must match or be castable to the type of the property. The Duke's Bookstore application demonstrates how to use the setProperty element and a scriptlet to set the current book for the database helper bean. For example, bookstore3/bookdetails.jsp uses the form: whereas bookstore2/bookdetails.jsp uses the form: The following fragments from the page bookstore3/showcart.jsp illustrate how to initialize a currency bean with a Locale object and amount determined by evaluating request-time expressions. Because the first initialization is nested in a useBean element, it is only executed when the bean is created.

Retrieving JavaBeans Component Properties There are several ways to retrieve JavaBeans component properties. Two of the methods (the jsp:getProperty element and an expression) convert the value of the property into a String and insert the value into the current implicit out object: For both methods, beanName must be the same as that specified for the id attribute in a useBean element, and there must be a getPropName method in the JavaBeans component. If you need to retrieve the value of a property without converting it and inserting it into the out object, you must use a scriptlet: Note the differences between the expression and the scriptlet; the expression has an = after the opening % and does not terminate with a semicolon, as does the scriptlet. The Duke's Bookstore application demonstrates how to use both forms to retrieve the formatted currency from the currency bean and insert it into the page. For example, bookstore3/showcart.jsp uses the form whereas bookstore2/showcart.jsp uses the form: The Duke's Bookstore application page bookstore2/showcart.jsp uses the following scriptlet to retrieve the number of books from the shopping cart bean and open a conditional insertion of text into the output stream: 0) {%> Although scriptlets are very useful for dynamic processing, using custom tags (see Custom Tags in JSP Pages) to access object properties and perform flow control is considered to be a better approach. For example, bookstore3/showcart.jsp replaces the scriptlet with the following custom tags:

Figure 4-4 summarizes where various types of objects are stored and how those objects can be accessed from a JSP page. Objects created by the jsp:useBean tag are stored as attributes of the scope objects and can be accessed by jsp:[get|set]Property tags and in scriptlets and expressions. Objects created in declarations and scriptlets are stored as variables of the JSP page's servlet class and can be accessed in scriptlets and expressions.

Figure 4-4 Accessing Objects From a JSP Page