jueves, 20 de agosto de 2009

Generación dinámica de un JNLP

Aunque trabajo como programador informático, no suelo poner muchas entradas de esa temática. Sin embargo, en el nuevo aire

que le he dado al blog, quiero que este tipo de entradas, puedan llegar a ser algo más habituales.

Empecemos.

Lo primero es explicar qué es la tecnología JNLP. Dentro de unos días la explicaré mejor.

Sin hacer referencia a aburridas y largas explicaciones en inglés, JNLP es simple y llanamente un tecnología de JAVA que nos

permite ejecutar de forma on-line una aplicación de escritorio.

Es decir, centralizamos el recurso (un archivo .jar) en el servidor y con una página web que haga referencia a un archivo

JNLP (en el fondo, un archivo XML), podemos iniciar el programita desde cualquier parte, con la ventaja de que en caso de

tener que actualizar el programa, Java Web Start se encarga de comprobar y hacer la actualización pertinente si fuese

necesario.

En una linea : JNLP es un acceso directo desde una página web a un programa remoto.

Aún así, para quien quiera más información: wikipedia/jnlp

Dicho esto, vamos al mehollo.

No voy a poner código inncesario que con un poco de google puedes encontrar por mil sitios. Simplemente me voy a limitar a

explicar lo que hay que hacer y poner el código que crea conveniente.

Ejemplo 1:

Queremos arrancar on-line una aplicacion estática (siempre es la misma)

1- Creamos una aplicación web, y en el index.jsp ponemos un enlace al archivo .jnlp:

<center>
<
a href="recursos/miArchivo.jnlp">
<img src="recursos/imagenes/reloj.gif">
</
a>
</
center>


2- Lógicamente creamos el archivo jnlp.

3- Si la aplicación hace uso de recursos locales del ordenador, bases de datos, etc. habrá que firmarla (cómo se firma, lo explicaré en otra entrada en unos días, intentaré enlazarla desde aquí).
Naturalmente, una aplicación puede tener librerias, es decir, varios .jar más a parte del principal. Habrá que firmarlos todos.
Cuidado con las dobles firmas. Algunas librerías vienen firmadas de origen. Deberéis entrar en el .jar (al fin y al cabo, no son más que archivos comprimidos) y borrar del META-INF las referencias a archivos .DSA, .RSA y .SF que no sean de tu firma

Y ya está. ¿Fácil no?



Ejemplo 2:

Complicamos la cosa un poco.
Imaginemos que queremos arrancar una aplicación que nos mostrará un texto que hemos escrito en la página web.
Realmente esto es una chorrada, pero si el texto "dinamico" fuese una clave generada a partir de un usuario y contraseña para validar el inicio de la aplicación... la cosa va cogiendo color.
A lo que vamos:

1- Creamos una aplicación web.
1.1- En el index.jsp ponemos algo como esto:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>JSP Page</title>
</head>
<body>
<center>
<form name="Generador" action="GeneradorJNLP" method="POST">
<div style="
border-style:solid;
border-width:1px;
width:200px;
height:80px;">
<center>
<br>
<input type="text" name="valor" style="width:180px"/><br>
<a href="#" onclick="Generador.submit();">Iniciar</a>
</center>
</div>
</form>
</center>
</body>
</html>


Contiene un input para recoger el texto y un enlace que realiza un submit() del formulario.

2.2 Creamos en la aplicación web un servlet, en mi caso llamada "GeneradorJNLP" que es donde apunta el action del formulario

Del servlet lo que nos interesa es:

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
String valor = (String) request.getParameter("valor");
String jnlp = getJnlp(valor);
try {
response.setContentType("application/x-java-jnlp-file");
out.print(jnlp);
} finally {
out.close();
}
}
}


Recogemos el valor del input anterior con :
(String) request.getParameter("valor");


Mediante
response.setContentType("application/x-java-jnlp-file");
le decimos que vamos a mandar un documento de tipo JNLP para que el navegador lo interprete correctamente.

En getJnlp(valor) :

public String getJnlp(String valor) {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n" +
"<jnlp codebase="">

(...)

"<application-desc main-class=\"inicio.Main\"> \n" +
" <argument>" + valor + "</argument> \n" +
"</application-desc>";

}

Fijaros que aunque en todos los sitios con informacion sobre JNLP ponen en esa linea href="nombreDelJNLP", yo no lo he puesto.

La explicación es fácil.

No queremos generar un archivo de forma física, por lo que no podemos tampoco poner un href ya que no existe una dirección que linkar.

Al poner href le obligamos a JNLP a que el aunque en el archivo que nos descarguemos ponga misa, ejecute el archivo jnlp que hay en el servidor. Así se evitan posibles manipulaciones locales.

En nuestro caso, podemos prescindir de esto.

Por otra parte, el valor lo hemos incluído como un argumento en la etiqueta de jnlp que indica cual es la clase principal (donde se encuentra el método public static void main(String args[]) )

Este argumento, luego podrá ser recuperado en el programa accedidiendo a la posición 0 del vector args[]. Y una vez recuperado, ya lo podremos tratar.

3.Naturalmente, y como en el ejemplo anterior, firmar todos los recursos .jar que sean necesarios.


Bueno, aquí la cosa se ha complicado un poco con relación al ejemplo 1, pero aún así, ¿sigue siendo fácil no?

Quizá se me ha pasado alguna parte, o quizá algo podría estar mejor explicado, no lo sé. Si mal no recuerdo, es la primera vez que posteo un tema relacionado con programación.
Así que todas las correcciones en cuanto a sintaxis, explicación, ejemplos, código, u otras cosas, las aceptaré y tendré muy en cuenta.

Naturalmente como siempre, espero vuestros comentarios.

(Actualización: podéis ver la segunda parte, aquí)

11 comentarios:

  1. Hola estoy un poco verde en el tema de deployar servicios Web, podrias detallar un poco como se ejecuta? Yo lo he hecho de la siguiente manera:
    -Creo en el eclipse un Web Dynamic Project
    -Creo un fichero .jsp como el que tu tienes y se lo agrego a la raíz de ese proyecto.
    -Creo un servlet como el tuyo.
    -Ejecuto el Proyecto Web en un tomcat, y en el navegador pongo la siguiente URL:
    http://localhost:8080/GetParametrosLiDAR/ServletGetParameters?valor=holamundo

    donde GetParametrosLiDAR es el nombre del proyecto y ServletGetParameters es el nombre del servlet.

    Tambien e probado esto:
    http://localhost:8080/GetParametrosLiDAR/ServletGetParameters/index.jsp


    Gracias por todo.


    -PD: en el index.jsp la palabra Iniciar no la coge bien y no se para que sirve

    ResponderEliminar
  2. Buenas!
    Gracias por el comentario.
    Si no te molesta, te contesto con una entrada del blog, así quizá pueda servir de ayuda a más gente.

    Gracias!

    ResponderEliminar
  3. Buenas noches amigo, hoy en mi trabajo reventó una inicidencia con la seguridad del password de la base de datos el cual está quemado en el jar principal, tu entrada me ha dado ideas para afrontar esta situación y como dices mejorarla poco a poco! gracias luego te cuento como quedó!

    Se que es muy viejo el post espero que lo leas!

    ResponderEliminar
    Respuestas
    1. Buenas!
      Leo todos los comentarios, me llegan al correo jejeje.
      Yo me enfrenté a una problemática parecida, una conexión a BD desde un jar, que tenía dentro la contraseña de BD.

      Al final, hice un servicio web donde el jar sólo era el cliente que mediante SOAP hacía peticiones a un servicio que ya era el que tenía guardada la contraseña.
      El siguiente problema fue, que al ser un servicio web, quien lo conozca podría hacer una una petición y modificar la bd de forma no autorizada.
      En ese caso, si sólo quieres que sea tu jar el que se conecte a tu WS (WebService), tienes que tener un protocolo de conexión, ya que otra contraseña dentro de tu jar para "garantizar la conexión" será como el problema inicial.

      Mi solución fue un "protocolo de conexión", dicho de otro modo: que el jar tenía que hacer peticiones a diferentes funciones del WS en un orden concreto, (como un saludo de bronx) y ese orden lo intenté ofuscar en el jar todo lo posible, de forma que un posible atacante prefiriese dedicarse a contar pétalos de margaritas, antes que a "entender" el código.
      Además también en ese saludo, también hacía paso de parámetros que tenían que corresponder a unos determinados valores.
      Vamos, que intenté putear lo máximo posible a un posible atacante.

      Quizá hay más formas, más seguras, y más fáciles de hacer, pero sobre este tema no hay excesiva información, y no se me ocurrieron :(
      Y seamos sinceros, un jar es casi como un texto plano que le das a alguien, y ahí no puedes poner nada que sea crítico.
      Así que la única forma de asegurar una conexión, es ofuscar ese texto lo máximo como sea posible.

      Espero que te haya servido de ayuda.
      Muchísimas gracias por visitar mi blog y comentar!!!
      Un saludo!!!

      Eliminar
  4. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  5. Hola, Gracias por el aporte. Tengo un problema con mi aplicacion que tiene una estructura similar a la que comentas. Genero el .jnlp de manera dinamica y paso argumentos al .jar firmado, pero en algunos ordenadores pasa que estos argumentos llegan vacios. Estos argumentos se devuelven al servidor junto con un parametro mas que tambien llega vacio al servidor web. Que crees que puede ser?

    ResponderEliminar
  6. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  7. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  8. Hola no se si mi puedes ayudar lo que no tengo muy claro es si poner o no el href o dejarlo vacio, o ponerlo como esta que es lo que vi en otra pagina.
    Además cuando llamo al servlet desde un BEAN(algo no usual pero es lo unico que se me ocurre)
    bean.java esta asi:

    public String recogerHuella() {
    FacesContext context = FacesContext.getCurrentInstance();
    String baseURL = context.getExternalContext().getRequestContextPath();
    if(param==null || param.isEmpty()){
    return null;
    }
    String url = baseURL + "/launch?param1="+param;
    try {
    String encodeURL = context.getExternalContext().encodeResourceURL(url);
    context.getExternalContext().redirect(encodeURL);
    } catch (Exception e) {
    } finally {
    context.responseComplete();
    }
    return null;
    }

    donde launch es my servlet.
    Servlet: launch.java

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    PrintWriter out = response.getWriter();
    String valor = (String) request.getParameter("param1");
    String jnlp = getJnlp(valor);
    try {
    /* TODO output your page here. You may use following sample code. */
    response.setContentType("application/x-java-jnlp-file");
    out.print(jnlp);
    } finally {
    out.close();
    }
    }

    // No lo pongo en formato por que no me deja
    public String getJnlp(String valor) {
    jnlp codebase=\"http://192.168.0.17:8080/SecugenWeb/huella/\ href=launch.jnlp?param1=+valor+
    j2se version=1.8+
    jar href=JSGD.jar main=true
    application-desc main-class=jsgd.JSGD
    argument: valor
    application-desc
    }

    En el cliente parece que me busca un archivo launch.jnlp en la el directorio:http://192.168.0.17:8080/SecugenWeb/huella/. Lo que se supone no quiero hacer porque lo estoy generando en el servlet.
    Muchas Gracias.

    ResponderEliminar
    Respuestas
    1. Lei la segunda parte: y dice que no le ponga el href pero cuando le descargo en el cliente me sale un error:
      java.lang.NullPointer Exception.
      A que se podria deber??

      Eliminar
    2. Se que no es estoy yendo poco a poco y con calma. Me sale: esto
      Missing applicacition name manifest attirbute for: http://192.168.0.17:8080/SecugenWeb/huella/JSGD.jar

      Eliminar