Skip to main content
Imagen decorativa con bola del mundo y usuario

Recuperar el usuario que invoca un método de un servicio REST (JAX-RS)

En esta entrada veremos cómo recuperar información del usuario que invoca un método de nuestra API REST, siempre que implemente el estándar JAX-RS. Para ello haremos uso del contexto de seguridad (SecurityContext) que podemos inyectar tanto en nuestro método como en la clase.

El contexto de seguridad  almacena información relativa a la seguridad dentro del contexto de la petición. Entre los métodos que tenemos que implementar está “isUserInRole” que devuelve un booleano indicando si el usuario logado tiene el rol pasado como parámetro al método. También almacena el nombre del usuario.

Podemos utilizar la información del contexto de seguridad, por ejemplo, para realizar alguna operación especial o comprobación, en función de los roles del usuario que realiza la petición. También podemos recuperar el usuario que realiza una determinada operación para guardar esa información como parte de la auditoría de nuestra aplicación.

A continuación mostramos como inyectarlo y usarlo en un método:

@GET
@Produces({MediaType.APPLICATION_JSON+";charset=utf-8"})
@Secured
public Pelicula findPeliculas(@Context SecurityContext secContext, @QueryParam("titulo") String titulo, @QueryParam("nombreActor") String nombreActor) throws Exception{
		
		//Podemos comprobar si el usuario tiene un rol determinado
		if(secContext.isUserInRole("ADMINISTRADOR")) {
            //Realizar alguna operación específica para el administrador
        }

		//Podemos recuperar el nombre del usuario
		String nombre = null;
        Principal p = secContext.getUserPrincipal();
        if(p != null) {
            nombre = p.getName();
        }
		        
        // Habría que llamar a un servicio de búsqueda de películas
        return getPeliculaEjemplo(); 
    }

La anotación @Context es la que nos permite inyectar de un modo sencillo el contexto de seguridad.

Si queremos inyectarlo como atributo de la clase que contiene los métodos pondríamos:

@Path("/pelicula")
public class PeliculaResource {

    @Context SecurityContext secContext;

...

Espero que esta entrada te haya resultado útil. Por favor, valórala y deja tu comentario si tinenes cualquier duda.

imagen decorativa de candado virtual

Roles (autorización) en servicios REST con JAX-RS y token JWT

En esta entrada explicaremos como añadir una autorización basada en roles a un servicio REST Java hecho con JAX-RS y securizado con token JWT.

Las herramientas que debemos tener instaladas en nuestro equipo para el ejemplo son:

  • JDK 8
  • Un servidor Java. Para nuestro ejemplo utilizaremos Tomcat 8

Debemos tener también nuestra aplicación web Java creada con algunos servicios REST. Si no es así, puedes ver como crearla de forma sencilla en esta página: https://www.infointernet.es/internet-avanzado/java/crear-servicios-rest-json-una-aplicacion-web-java/.

En la siguiente entrada podemos ver cómo proteger los servicios REST mediante el uso de token JWT, con la librería JJWT: http://www.infointernet.es/internet-avanzado/java/securizar-servicios-rest-java-jax-rs-jwt/

Una vez que tenemos nuestros servicios REST protegidos con usuario y contraseña, es el momento de añadir autorización por roles. De este modo podremos dar permisos a ciertos usuarios para realizar determinadas operaciones, mientras se los denegamos a otros usuarios.

Usar la anotación @RolesAllowed

La anotación @RolesAllowed pertenece al estándar de Java (JSR 250). Se puede utilizar tanto en clases como en métodos concretos. Sirve para definir los roles que pueden acceder a dichas clases o métodos. Siguiendo con nuestro ejemplo, si tenemos un rol “ADMINISTRADOR” que es el único que puede ejecutar un método para añadir películas, pondríamos la anotación en dicho método del siguiente modo:

    @PUT
    @Secured
    @RolesAllowed({"ADMINISTRADOR"})
    public void addPelicula() throws Exception{
		
        // Habría que llamar a un servicio para crear películas
 
    }

La anotación @Secured es la que nos creamos a medida en el ejemplo de securización para asociar el método al filtro de seguridad.

Añadir la información del usuario y sus roles al token de seguridad (JWT)

En el cuerpo (playload) de un token JWT puede viajar cualquier propiedad (claim) que nosotros creemos. En nuestro caso, crearemos una llamada “roles” en la que pondremos los roles del usuario separados por coma. El nombre de usuario viajará en una de las propiedades por defecto que define el estándar y que se denomina “subject”. Con esta información será suficiente para saber en cada petición si un usuario tiene permisos para ejecutar un método o no, según las anotaciones @RolesAllowed que le afecten.

En nuestro ejemplo, tendremos que modificar el método utilizado para crear el token (JJWT). Ahí añadiremos una propiedad inicial con los roles de usuario logado. Dichos roles provendrán de una base de datos, etc. De este modo nuestro método de creación del token quedaría así:

private String issueToken(String login, String roles) {
    	//Calculamos la fecha de expiración del token
    	Date issueDate = new Date();
    	Calendar calendar = Calendar.getInstance();
    	calendar.setTime(issueDate);
    	calendar.add(Calendar.MINUTE, 60);
        Date expireDate = calendar.getTime();
        
		//Creamos el token
        String jwtToken = Jwts.builder()
        		.claim("roles", roles)
                .setSubject(login)
                .setIssuer("http://www.infointernet.es")
                .setIssuedAt(issueDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, RestSecurityFilter.KEY)
                .compact();
        return jwtToken;
    }

Hemos introducido los roles en el propio token para no tener que estar haciendo peticiones a base de datos en cada petición para recuperar los roles del usuario que realiza dicha petición. Los datos que viajan en el token son seguros e inalterables, porque en caso de modificación, el token dejaría de ser válido. De ahí que podamos utilizarlo para almacenar información de autorización.

Crear un bean que represente al usuario y que implemente Principal

Crearemos un bean con las propiedades del usuario logado que deseemos tener accesibles en nuestros métodos REST. Dicha clase debe implementar la interfaz del estándar Java llamada “Principal“.

public class User implements Principal {
	
	private String username;
	private List roles;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public String getName() {
		return username;
	}

	public List getRoles() {
		return roles;
	}

	public void setRoles(List roles) {
		this.roles = roles;
	}
	

}

 

Crear una clase que implemente SecurityContext

La interfaz SecurityContext forma parte del estándar de Java y sirve para almacenar información relativa a la seguridad. Entre los métodos que tenemos que implementar está “isUserInRole” que devuelve un booleano indicando si el usuario logado tiene el rol pasado como parámetro al método. Este método es el que se invocará con la anotación @RolesAllowed y determinará los permisos en la aplicación.

Nuestra clase quedaría así:

public class MyApplicationSecurityContext implements SecurityContext {
	
	private User usuario;
	private boolean secure;
	
	public MyApplicationSecurityContext(User usuario, boolean secure){
		this.usuario = usuario;
		this.secure = secure;
	}

	@Override
	public String getAuthenticationScheme() {
		return SecurityContext.FORM_AUTH;
	}

	@Override
	public Principal getUserPrincipal() {
		return usuario;
	}

	@Override
	public boolean isSecure() {
		return secure;	
	}

	@Override
	public boolean isUserInRole(String rol) {
		if (usuario.getRoles() != null) {
            return usuario.getRoles().contains(rol);
        }
        return false;
	}

}

Podemos ver que nuestra clase de contexto de seguridad almacena el bean con el usuario logado que hemos creado anteriormente. El atributo “secure” es un booleano que indica si el acceso es por https.

Modificar el filtro de seguridad para crear el contexto de seguridad en cada petición

Ahora debemos modificar la clase RestSecurityFilter que creamos en el ejemplo de securización. Crearemos una instancia del contexto de seguridad (MyApplicationSecurityContext) y la añadiremos al contexto de las peticiones (ContainerRequestContext) para que esté accesible en nuestros métodos y pueda actuar correctamente la anotación @RolesAllowed.

@Provider
@Secured
@Priority(Priorities.AUTHENTICATION)
public class RestSecurityFilter implements ContainerRequestFilter {
	
	public static final Key KEY = MacProvider.generateKey();
 
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
 
        // Recupera la cabecera HTTP Authorization de la petición
        String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
 
        try {
        	// Extrae el token de la cabecera
            String token = authorizationHeader.substring("Bearer".length()).trim();
 
            // Valida el token utilizando la cadena secreta
            Jws claims = Jwts.parser().setSigningKey(KEY).parseClaimsJws(token);
            
            //Creamos el usuario a partir de la información del token
            User usuario = new User();
            usuario.setUsername(claims.getBody().getSubject());
            String roles = (String) claims.getBody().get("roles"); 
            usuario.setRoles(Arrays.asList(roles.split(",")));
            
            // Creamos el SecurityContext
            MyApplicationSecurityContext secContext = new MyApplicationSecurityContext(usuario, requestContext.getSecurityContext().isSecure());
            
            //Seteamos el contexto de seguridad
            requestContext.setSecurityContext(secContext);
            
        } catch (Exception e) {
            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
        }
    }
}

Como podemos ver en los comentarios, estamos utilizando la información que viaja en el token para crear el usuario logado y el contexto de seguridad con los roles.

Registrar RolesAllowedDynamicFeature (Jersey)

En nuestro ejemplo estamos utilizando Jersey como implementación de JAX-RS. Para que funcionen las anotaciones @RolesAllowed en los métodos, debemos registrar la funcionalidad RolesAllowedDynamicFeature .  Lo haremos en la clase de creación aplicación REST:

@ApplicationPath("rest")
public class Application extends ResourceConfig {
    public Application() {
        packages("es.infointernet.rest");
        register(JacksonFeature.class);
        register(RolesAllowedDynamicFeature.class);
    }
}

Puedes descargarte el código del ejemplo en: https://github.com/mpecero/jaxrs-jwt-example

Espero que esta entrada te haya resultado útil. Por favor, valórala y deja tu comentario si tinenes cualquier duda.

Securizar servicios REST Java: JAX-RS con JWT

En esta entrada mostraremos como utilizar tokens para securizar nuestros servicios REST hechos con JAX-RS. Los token los crearemos con la librería JJWT, que implementa el estándar JWT, que es el habitual para la creación de tokens.

Podríamos haber securizado los servicios mediante sesiones, como cualquier otra aplicación Java, sin embargo, esto no es lo habitual ni lo recomendado para servicios REST sin estado. Podéis ver más información al respecto en: https://ponyfoo.com/articles/json-web-tokens-vs-session-cookies

Las herramientas que debemos tener instaladas en nuestro equipo para el ejemplo son:

  • JDK 8
  • Un servidor Java. Para nuestro ejemplo utilizaremos Tomcat 8

Debemos tener también nuestra aplicación web Java creada con algunos servicios REST. Si no es así, puedes ver como crearla de forma sencilla en esta página: https://www.infointernet.es/internet-avanzado/java/crear-servicios-rest-json-una-aplicacion-web-java/.

Qué es JWT

JSON Web Token (JWT) es un estándar (RFC-7519 ) para crear un token de acceso.

Estos tokens están formados por tres partes:

  • Cabecera (header): es una cadena JSON cifrada en Base64 que contiene el tipo de token y la codificación utilizada para la firma
     Header = '{"alg": "HS256", "typ": "JWT"}'
  • Cuerpo (payload): Es un conjunto de información en formato JSON que contiene los “claims”, que no son más que un conjunto de campos predefinidos, así como algunos propios que podemos añadir, como los roles.
    Payload = '{“sub”:”j.rodriguez”,"iat": "1424180385", "exp": "1424190385",”roles”:”a,b,c”}'
  • Firma (signature): Está formado por la cabecera y cuerpo, encriptado con una clave secreta. Esta parte es la que garantiza la integridad de los datos, es decir, que los datos anteriores no han sido alterados:
     clave = 'secretkey'
     unsignedToken = encodeBase64Url (encabezado) + '.' + EncodeBase64Url (carga útil)
     signature = HMAC-SHA256 (clave, unsignedToken)
    

Estas partes se unen por un “.”. De este modo, el Token resultante podría tener un aspecto como el siguiente:

token = eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MDA5OTY4MDIsImV4cCI6MTUwMTAwMDQwMn0.GI-e8ala__SygMKviTShtLK9gXMTOKQPn0ktUh31988XyyGMBo4MbWLa1t71ybNuYplKhSjAaasxKbHWiMNwTA

Esta información viajará normalmente en la cabecera “Authorization” en los accesos a recursos protegidos.

Authorization: Bearer token

Puedes encontrar más información en https://carlosazaustre.es/blog/que-es-la-autenticacion-con-token/

Paso 1: Añadir dependencias

Debemos añadir a nuestro proyecto la dependencia de JJWT, que será la que nos permitirá generar y validar el token en formato JWT.

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.7.0</version>
</dependency>

Paso 2: Crear el filtro

El filtro será el encargado de interceptar las peticiones a recursos REST protegidos y validar el token recibido en la cabecera. Para ellos debe implementar la interfaz ContainerRequestFilter, que forma parte del estándar JAX-RS.

@Provider
@Secured
@Priority(Priorities.AUTHENTICATION)
public class RestSecurityFilter implements ContainerRequestFilter {
	
    public static final Key KEY = MacProvider.generateKey();
 
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
 
        // Recupera la cabecera HTTP Authorization de la petición
        String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
 
        try {
        	// Extrae el token de la cabecera
            String token = authorizationHeader.substring("Bearer".length()).trim();
 
            // Valida el token utilizando la cadena secreta
            Jwts.parser().setSigningKey(KEY).parseClaimsJws(token);
 
        } catch (Exception e) {
            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
        }
    }
}

Para generar la cadena secreta hemos utilizado la clase “MacProvider” de la propia libreria JWT. El método usado genera una clave secreta aleatoria de 512 bits. Podría utilizarse también alguna clave almacenada en un keystore.

La anotación “@Provider” será la que nos permitirá registrar automáticamente nuestro filtro en nuestra aplicación REST.

Paso 3: Anotación para vincular el filtro las clases y métodos protegidos

El filtro creado anteriormente no debe ejecutarse siempre, sino sólo en aquellos casos en los que el recurso esté protegido. Para ello crearemos una anotación que utilizará la meta-anotación “@NameBinding”, que forma parte de JAX-RS.

@javax.ws.rs.NameBinding
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
public @interface Secured {

}

Paso 4: Securizar los recursos con la nueva anotación

Los métodos y clases que queramos securizar deberán ir precedidos por la anotación “@Secured” que hemos creado en el paso anterior.

@Path("/pelicula")
public class PeliculaResource {
	
    @GET
    @Path("{idPelicula}")
    @Secured
    @Produces({MediaType.APPLICATION_JSON+";charset=utf-8"}) 
    public Pelicula getPelicula(@PathParam("idPelicula") Integer idPelicula) throws Exception{
		
        //Habría que llamar a un servicio que devolviera la película con el id pasado como parámetro
        return getPeliculaEjemplo(); 
    }
    ...

Cuando se invoque al método del ejemplo anterior, se ejecutará previamente el filtro, comprobando que viaja la cabecera “Authorization” con un token válido. En caso de que haya cualquier problema, se devolvería un error HTTP 401. Si todos es correcto, se daría paso a la ejecución del método del ejemplo.

Paso 5: Crear un método para la obtención del token

Creamos un nuevo recurso con un método público que nos permitirá realizar la autenticación y obtener un token en caso positivo.

@Path("/usuario")
public class UsuarioResource {
 
    @GET
    @Path("/login")
    public Response authenticateUser(@QueryParam("login") String login,
    		@QueryParam("password") String password) {
        try {
 
            // Aquí iría el código de validación del usuario y contraseñas proporcionados,
        	// por ejemplo validándolo contra una base de datos...
            //authenticate(login, password);
        	
            // Si todo es correcto, generamos el token
            String token = issueToken(login);
 
            // Devolvemos el token en la cabecera "Authorization". 
            // Se podría devolver también en la respuesta directamente.
            return Response.ok().header(HttpHeaders.AUTHORIZATION, "Bearer " + token).build();
 
        } catch (Exception e) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }
    }
 
    private String issueToken(String login) {
    	//Calculamos la fecha de expiración del token
    	Date issueDate = new Date();
    	Calendar calendar = Calendar.getInstance();
    	calendar.setTime(issueDate);
    	calendar.add(Calendar.MINUTE, 60);
        Date expireDate = calendar.getTime();
        
        //Creamos el token
        String jwtToken = Jwts.builder()
                .setSubject(login)
                .setIssuer("http://www.infointernet.es")
                .setIssuedAt(issueDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, RestSecurityFilter.KEY)
                .compact();
        return jwtToken;
    }
}

Hemos utilizado para la creación del token la misma clave secreta que en la validación, para que todo funcione correctamente.

Puedes descargarte el código del ejemplo en: https://github.com/mpecero/jaxrs-jwt-example

Espero que esta entrada te haya resultado útil. Por favor, valórala y deja tu comentario si tinenes cualquier duda.

CORS

Peticiones Cross-domain con Javascript en servicios REST con JAX-RS (CORS)

En este artículo explicaremos como habilitar CORS en servicios REST hechos con JAX-RS, para resolver el problema de acceso a dominios cruzados con Javascript.

El problema

Si estamos desarrollando aplicaciones web modernas con Javascript (Ionic, Angular, etc.) es posible que nos hayamos encontrado con el siguiente error a la hora de realizar nuestras llamadas AJAX (XMLHttpRequest) a los servicios REST:

XMLHttpRequest cannot load http://miservicio.es/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://mi.app' is therefore not allowed access.

Es un mecanismo de seguridad de los navegadores modernos, que impiden que se puedan realizar peticiones AJAX en una aplicación que se está ejecutando en un dominio y puerto a un servicio que está ejecutándose en otro dominio y/o puerto diferente. En concreto estamos violando la política del mismo origen (SOP).

La solución: CORS

Cross-origin resource sharing (CORS) es un mecanismo estándar definido por el W3C que permite que se hagan llamadas JavaScript con XMLHttpRequests a otro dominio diferente al que originó el código JavaScript. Tales solicitudes de “dominio cruzado” estarían prohibidas por los navegadores web, según la política de seguridad SOP.

Para habilitarlo tendremos que incluir una nueva cabecera HTTP en la respuesta: Access-Control-Allow-Origin . Si recuerdas el mensaje de error anterior, esto es exactamente lo que el navegador está tratando de decirte. Cuando un navegador recibe una respuesta de una fuente de origen cruzado, comprobará si hay cabeceras CORS. Si el origen especificado en el encabezado de respuesta coincide con el origen actual, permite el acceso de lectura a la respuesta. De lo contrario, obtendrá el mensaje de error.

Es más práctico que sólo permitir peticiones de mismo origen y es más seguro que simplemente permitir todas las solicitudes de origen cruzado.

Implementar CORS con Jersey (JAX-RS)

Si tenemos nuestros servicios REST implentados con Jersey, como explicamos en un articulo anterior, podemos aplicar CORS mediante un filtro. Este filtro modificará las peticiones y respuestas inclyendo las cabeceras definidas por CORS.

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;

@Provider
public class ResponseCorsFilter implements ContainerResponseFilter {

	@Override
	public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException {
		response.getHeaders().add("Access-Control-Allow-Origin", "*");
		response.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, Authorization");
		response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
	}
}

En el ejemplo anterior, el filtro ResponseCorsFilter siempre agrega las siguientes cabeceras:

  • Access-Control-Allow-Origin: El “*” significa que la solicitud puede provenir de cualquier dominio. De esta manera hacemos que la API REST sea pública.
  • Access-Control-Allow-Methods: indica que se permiten los métodos GET, POST, DELETE, PUT para acceder al recurso.
  • Access-Control-Allow-Headers: indica que los encabezados Origin, Content-Type, Acept y Authorization se pueden utilizar al realizar la solicitud.

Los filtros en los servicios JAX-RS deben implementar la interfaz ContainerResponseFilter. Para registrar el filtro en nuestra aplicación ulizamos la anotación @Provider.

Espero que te haya sido útil el artículo. Si tienes cualquier duda o sugerencia, no dudes en escribirme.

Crear servicios REST con JSON en una aplicación web Java

Vamos a explicar cómo crear servicios REST que trabajen con JSON en una aplicación web Java.

Los servicios REST que devuelven JSON son cada vez más utilizados, porque se adaptan muy bien a las necesidades de los nuevos frameworks JavaScript, tanto para aplicaciones web (Angular, React,…) como para aplicaciones móviles híbridas (Ionic).

Las herramientas que debemos tener instaladas en nuestro equipo para el ejemplo son:

  • JDK 8
  • Un servidor Java. Para nuestro ejemplo utilizaremos Tomcat 8

Debemos tener también nuestra aplicación web Java creada. Si no es así, puedes ver como crearla de forma sencilla en esta página: http://www.infointernet.es/internet-avanzado/java/crear-una-aplicacion-web-java-eclipse-maven/.

Existen dos alternativas principales a la hora de crear los servicios REST en Java:

  • Con Spring MVC: útil si ya utilizamos este framework en nuestro proyecto o estamos muy habituados a él.
  • Con JAX-RS: es la especificación de Java para la creación de servicios REST. Esta será la opción que utilicemos en esta entrada, al ser la más simple y estándar.

Como hemos comentado anteriormente JAX-RS es una especificación, por lo que debemos utilizar alguna libería que lo implemente: RESTEasy (integrado en WildFly), Jersey (integrado en Glassfish), etc.

En nuestro ejemplo optaremos por Jersey, que es la de uso más extendido. Además, le añadiremos un módulo para trabajar con JSON llamado Jackson.

Paso 1: Añadir dependencias

Una vez tenemos nuestro proyecto Java gestionado con Maven, le añadiremos las dos siguientes dependencias:

<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>2.25.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.25.1</version>
</dependency>

Paso 2: Crear los beans

En nuestro ejemplo crearemos dos beans, uno para las películas y otro para los actores:

   public class Actor {
	
	private String nombre;

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}	

   }

   public class Pelicula {
	
	private String titulo;
	
	private List actoresList;

	public String getTitulo() {
		return titulo;
	}

	public void setTitulo(String titulo) {
		this.titulo = titulo;
	}

	public List getActoresList() {
		return actoresList;
	}

	public void setActoresList(List actoresList) {
		this.actoresList = actoresList;
	}

   }

Paso 3: Crear la aplicación JAX-RS y desplegarla en el servidor

JAX-RS proporciona una clase abstracta agnóstica de despliegue llamada Application que nos permitirá definir el paquete raíz donde están los servicios (recursos) REST así como como los provider. En nuestro caso, crearemos una clase que extiende ResourceConfig, que a su vez extiende a la clase Application.

@ApplicationPath("rest")
public class Application extends ResourceConfig {
    public Application() {
        // Paquete donde están nuestros servicios REST
        packages("es.infointernet.rest"); 
        // Registramos el Provider de JSON de Jackson
        register(JacksonFeature.class);
    }
}

Con la anotación @ApplicationPath estamos registrando nuestra aplicación en el servidor, además de indicar que estará escuchando en el path “rest”. También podríamos registrarla como un servlet, pero no vamos a complicar el ejemplo.

Paso 4: Crear los servicios

Ahora ya solo nos quedaría crear las clases para los recursos, donde indicaremos las URL y parámetros que recibirán. Veamos un ejemplo:

@Path("/pelicula")
public class PeliculaResource {
	
    @GET
    @Path("{idPelicula}")
    @Produces({MediaType.APPLICATION_JSON+";charset=utf-8"}) 
    public Pelicula getPelicula(@PathParam("idPelicula") Integer idPelicula) throws Exception{
		
        //Habría que llamar a un servicio que devolviera la película con el id pasado como parámetro
        return getPeliculaEjemplo(); 
    }
	
    @GET
    @Produces({MediaType.APPLICATION_JSON+";charset=utf-8"}) 
    public Pelicula findPeliculas(@QueryParam("titulo") String titulo, @QueryParam("nombreActor") String nombreActor) throws Exception{
		
        // Habría que llamar a un servicio de búsqueda de películas
        return getPeliculaEjemplo(); 
    }
	
	
    /**
    * Servicio de ejemplo. 
    * @return
    */
    private Pelicula getPeliculaEjemplo(){
	List actores = new ArrayList();
        
        Actor a = new Actor();
        a.setNombre("Penélope Cruz");
        actores.add(a);
        
        Pelicula p = new Pelicula();
        p.setTitulo("Jamón Jamón");
        p.setActoresList(actores);
       
        return p;
    }
}

Vamos a explicar un poco las anotaciones:

  • @Path: Nos sirve para especificar la URL donde van a estar publicados nuestros servicios. Se puede aplicar a una clase y/o a un método. Puede recibir valores entre llaves que corresponderían a parámetros ( @Path(“{idPelicula}”))
  • @GET: Indica que a ese método hay que invocarlo por el método HTTP GET. Se suelen utilizar:
    • GET: Para recuperar recursos
    • POST: Para añadir recursos
    • PUT: Para modificar recursos
    • DELETE: Para borrar recursos
  • @Produces({MediaType.APPLICATION_JSON+”;charset=utf-8″}): Con esto indicamos que el método devuelve JSON codificado en UTF-8. Es importante la codificación, para que los caracteres acentuados en español se devuelvan correctamente.
  • @PathParam: Se utiliza para mapear los parámetros pasados por el path de la URL
  • @QueryParam: Se utiliza para mapear los parámetros pasados por la URL

Para invocar a los métodos anteriores deberíamos poner, por ejemplo:

  • Primer método: http://localhost:8080/<nombe_aplicacion>/rest/pelicula/125
  • Segundo método: http://localhost:8080/<nombe_aplicacion>/rest/pelicula?titulo=jamón

Existen muchas más anotaciones que ofrecen muchas más posibilidades, pero se salen del objetivo del ejemplo. Si quieres profundizar más, puedes ver algunos ejemplos aquí: https://jersey.java.net/documentation/latest/jaxrs-resources.html

Espero que te haya sido de utilidad este artículo. Si tienes cualquier duda o comentario no dudes en escribirme.

Puedes descargar el código del ejemplo aquí: DemoRest. También puedes descargar el ejemplo completo, con seguridad incluida, de: https://github.com/mpecero/jaxrs-jwt-example

En próximos artículos explicaré como securizar estos servicios.

Crear aplicación web Java con Eclipse

Crear una aplicación web java con Eclipse y Maven

Vamos a explicar cómo crear una aplicación web Java con Eclipse y Maven.

Las herramientas que debemos tener instaladas en nuestro equipo para el ejemplo son:

En primer lugar seleccionamos la opción de menú “File -> New -> Maven Project”.

menú File -> New -> Maven Project

Nos aparecerá un cuadro de diálogo donde debemos seleccionar la opción “Create a simple project (skip archetype selection)”. Existen muchos arquetipos para aplicaciones web, pero si queremos un proyecto limpio, lo mejor es crearlo sin arquetipo.

Cuadro de diálogo New Maven Project

Al pulsar “Next”, nos solicitará el id de grupo y el id del artefacto. Le ponemos el que deseemos. Además debemos seleccionar la opción de empaquetado “war”.

Para terminar pulsaremos “Finish” y automáticamente tendremos creado el proyecto.

Aquí no termina el trabajo, porque el proyecto que genera no tiene el fichero descriptor de despliegue (web.xml) y la versión de Java con la que aparece es la 5. Podemos corregir esto haciendo clic con el botón derecho sobre el proyecto y eligiendo en el menú contextual la opción “Properties”. Nos aparecerá un cuadro de diálogo donde elegiremos la opción de menú “Project Facets” y cambiaremos la versión de Java a la 1.8.

Project Facets con Java 8

En esa pantalla también desmarcaremos la opción “Dynamic Web Module” y pulsaremos “Apply”. Posteriormente volveremos a marcar la opción “Dynamic Web Module” y veremos como aparece un enlace abajo llamado “Futher configuration available”. La pulsaremos e introduciremos la ruta del directorio de contenidos (“src/main/webapp”) y marcaremos la opción para que cree el descriptor de despliegue. Seleccionaremos la versión 3.1 del “Dynamic Web Module” y pulsaremos “Ok” para terminar.

Configuración de Dynamic Web Project

Con esto hemos conseguido que Eclipse nos cree el descriptor de despliegue para la especificación de Servlet 3.1. Deberíamos asegurarnos de que nuestro servidor soporta dicha versión. Para el ejemplo utilizamos Tomcat 8 que si lo soporta (http://tomcat.apache.org/whichversion.html).

Podemos crear nuestra primera página haciendo clic con el botón derecho sobre la carpeta “src/main/webapp” y seleccionado “new -> JSP File”. La llamaremos “index.jsp” y será nuestra página de inicio.

Menú New -> JSP File

Ahora ya solo tenemos que añadir nuestra aplicación al servidor Java que tenemos en Eclipse y arrancarlo. Podemos acceder a la aplicación en cualquier navegador poniendo la dirección: http://localhost:8080/<nombre_proyecto>

Aquí os podéis descargar el código del proyecto que hemos creado: DemoWeb.zip

Podéis importarlo en vuestro Eclipse mediante la opción “File -> Import” y seleccionando en el cuadro de diálogo “Existing Maven Project”.

Espero que os haya sido de utilidad esta entrada. Si tienes cualquier duda o sugerencia, deja tu comentario.

Cables de red

Recuperar la IP del cliente en Java tras un balanceador

Podemos recupera la IP del cliente que accede a nuestra aplicación web Java mediante el código:

String ipAddress = request.getRemoteAddr();

Ahora bien, si el servidor está detrás de un balanceador de carga lo que obtendremos siempre con la sentencia anterior es la IP del balanceador. En estos casos debemos obtener la IP que viaja en alguna de las cabecera de la petición HTTP, mediante el código:

     public String getIpAddr(HttpServletRequest request) {
         final String[] HEADER_LIST = { 
            "X-Forwarded-For",
            "Proxy-Client-IP",
            "WL-Proxy-Client-IP",
            "HTTP_X_FORWARDED_FOR",
            "HTTP_X_FORWARDED",
            "HTTP_X_CLUSTER_CLIENT_IP",
            "HTTP_CLIENT_IP",
            "HTTP_FORWARDED_FOR",
            "HTTP_FORWARDED",
            "HTTP_VIA",
            "REMOTE_ADDR" 
        };

        for (String header : HEADERS_LIST) {
            String ip = request.getHeader(header);
            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip) && !isPrivateAdress(ip)) {
                return ip;
            }
        }
        return request.getRemoteAddr(); 
    }

    public boolean isPrivateAdress(String host) {
        try {
            return java.net.InetAddress.getByName(host).isSiteLocalAddress();
        } catch (UnknownHostException e) {
            LOGGER.warn("UnknownHostException en isPrivateAdress(String host)", e);
            return true;
        }
    }

El método “getIpAddr” devolvería la IP pública del cliente. Si lo que queremos es simplemente recuperar la IP pública o privada del cliente, aunque esté detrás de un proxy, tendríamos que quitar la comprobación de si la IP es privada:

     public String getIpAddr(HttpServletRequest request) {
         final String[] HEADER_LIST = { 
            "X-Forwarded-For",
            "Proxy-Client-IP",
            "WL-Proxy-Client-IP",
            "HTTP_X_FORWARDED_FOR",
            "HTTP_X_FORWARDED",
            "HTTP_X_CLUSTER_CLIENT_IP",
            "HTTP_CLIENT_IP",
            "HTTP_FORWARDED_FOR",
            "HTTP_FORWARDED",
            "HTTP_VIA",
            "REMOTE_ADDR" 
        };

        for (String header : HEADERS_LIST) {
            String ip = request.getHeader(header);
            LOGGER.debug("Contenido del header "+ header + " / "+ ip);
            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
                return ip;
            }
        }
        return request.getRemoteAddr(); 
    }

Espero que te haya resultado útil. Si tienes cualquier duda o problema con el código, déjame tu comentario.

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

CERRAR