Soporte CORS para servicios REST

Vamos a indicar los pasos para realizar la configuración de CORS en nuestros servicios Rest mediante anotaciones.

Configuración

Lo primero que necesitamos es añadir la siguiente configuración en el fichero web.xml.

	<context-param>
 		<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
 		<param-value>es.um.atica.jersey.cors.ResourceCorsFilterFactory</param-value>
 	</context-param>

Recordemos que el parámetro de contexto com.sun.jersey.spi.container.ResourceFilters se puede indicar el valor como una lista separada por ','. Por lo que si ya existe dicha declaración solo hay que añadir el es.um.atica.jersey.cors.ResourceCorsFilterFactory.

Ahora en los métodos de los servicios tenemos que utilizar la anotacion @CrossDomain que se puede indicar a nivel de clase o a nivel de método y tenemos los siguientes escenarios:

  • Si se indica solo a nivel de clase, se asocia a todos los métodos que expne el servicio Rest.
  • Si se indica solo a nivel de método, solo se asocia la configuración al metodo asociado a la anotación.
  • Si indica a nivel de clase y a nivel de método. La configuración indicada a nivel de método, sobreescribe la información indicada a nivel de clase.

La anotacion @CrossDomain tiene las siguientes porpiedades:

  • skip: boolean que con valor true indica que no se procese CORS para este método. Valor por defecto, false.
  • origins: String[] la lista de orígenes permitidos que son orígenes específicos. Valor por defecto, {}.

Por motivos de seguridad, no es apropiado utilizar el valor “*” en la propiedad origins.

  • allowCredentials: boolean que con valor true indica que el navegador debe enviar las credenciales como cookies en la solicitud. Valor por defecto, false.
  • allowedHeaders: String[] la lista de cabeceras de solicitud permitidos en la solicitud actual, permitiendo “*” para permitir todas las cabeceras. Valor por defecto, {}.
  • exposedHeaders: String[] la lista de cabeceras de la respuesta a los que el agente de usuario permitirá que el cliente acceda de la respuesta actual y que no sean cabeceras “simples”. Valor por defecto, {}.
  • maxAge: long la antigüedad máxima (en segundos) de la duración de la memoria caché para las respuestas de verificación previa (preflight). Valor por defecto, 300.
  • methods: RequestMethod[] la lista de métodos de solicitud HTTP admitidos. Valor por defecto, {}.

Ejemplos


CORS Simple


Primer ejemplo de petición CORS simple, donde en la anotación @CrossDomain indicamos:

  1. GET como método permitido para CORS.
  2. https://www.mydomain.com como origen valido para CORS.
    @CrossDomain(methods = RequestMethod.GET, origins = "https://www.mydomain.com")
    @Path("/simple")
    @GET
    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
    public Response simpleCorsTest() {
        return Response.status( 200 ).entity( "Cors Simple Ok." ).build();
    }


CORS con Preflight


Ejemplo de CORS con preflight o autorización previa, donde en la anotación @CrossDomain indicamos:

  1. GET y OPTIONS como métodos permitidos para CORS.
  2. https://www.mydomain.com como origen valido para CORS.
    @CrossDomain(methods = { RequestMethod.GET, RequestMethod.OPTIONS }, origins = "https://www.mydomain.com")
    @Path("/preflighted")
    @GET
    @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } )
    public Response preflightedCorsTest() {
        return Response.status( 200 ).entity( "Cors Simple Ok." ).build();
    }

Ahora además debemos añadir el método para gestionar el preflight. Para eso tenemos que indicar el método HTTP OPTIONS con el mismo @Path. Añadimos la anotación @CrossDomainOptions, para indicar que es un OPTIONS de CORS. La lógica interna recupera el método asociado para el @Path y recupera la información de @CrossDomain para preparar las respuesta. Este método realmente nunca se ejecuta, pero por la limitación de la API JAXRS de no podemos poner más de un método HTTP en el mismo método JAVA.

    @CrossDomainOptions
    @Path("/preflighted")
    @OPTIONS
    public Response preflightedCorsTestCorsOptions() {
        return null;
    }


CORS con Preflight con varios @Path iguales


Ejemplo de CORS con preflight o autorización previa, donde tenemos varios métodos con el mismo valor de @Path. En este caso para gestionar bien el preflight, debemos indicar en la propiedad method de la anotación @CrossDomainOptions el nombre del método correcto.

    @CrossDomain(methods = { RequestMethod.GET, RequestMethod.OPTIONS }, origins = "https://www.mydomain.com")
    @Path("/preflighted/{param}")
    @GET
    @Produces( MediaType.APPLICATION_JSON )
    public Response preflightedCorsTest2(@QueryParam("param") String param) {
        return Response.status( 200 ).entity( "Cors Simple Ok." ).build();
    }
 
    @CrossDomain(methods = { RequestMethod.GET, RequestMethod.OPTIONS }, origins = "https://www.mydomain.com")
    @Path("/preflighted/{param}")
    @GET
    @Produces( MediaType.APPLICATION_XML )
    public Response preflightedCorsTest3(@QueryParam("param") Integer param) {
        return Response.status( 200 ).entity( "Cors Simple Ok." ).build();
    }

Configuración para el preflight donde indicamos que el método al que va dirigido:

    @CrossDomainOptions( method = "preflightedCorsTest2" )
    @Path("/preflighted/{param}")
    @OPTIONS
    public Response preflightedCorsTestCorsOptions2() {
        return null;
    }


CORS con Preflight con varios @Path iguales y métodos con el mismo nombre


Ejemplo de CORS con preflight o autorización previa, donde tenemos varios métodos con el mismo valor de @Path y que además tienen el mismo nombre. En este caso para gestionar bien el preflight, debemos indicar en la anotación @CrossDomainOptions, la propiedad method con el nombre del método correcto, y en la propiedad parameterTypes el tipo de los parámetros del método.

    @CrossDomain(methods = { RequestMethod.GET, RequestMethod.OPTIONS }, origins = "https://www.mydomain.com")
    @Path("/preflighted/{param}")
    @GET
    @Produces( MediaType.APPLICATION_JSON )
    public Response preflightedCorsTest(@QueryParam("param") String param) {
        return Response.status( 200 ).entity( "Cors Simple Ok." ).build();
    }
 
    @CrossDomain(methods = { RequestMethod.GET, RequestMethod.OPTIONS }, origins = "https://www.mydomain.com")
    @Path("/preflighted/{param}")
    @GET
    @Produces( MediaType.APPLICATION_XML )
    public Response preflightedCorsTest(@QueryParam("param") Integer param) {
        return Response.status( 200 ).entity( "Cors Simple Ok." ).build();
    }

Configuración para el preflight donde indicamos que el método y el tipo de sus parçametros al que va dirigido:

    @CrossDomainOptions( method = "preflightedCorsTest", parameterTypes = { Integer.class }  )
    @Path("/preflighted/{param}")
    @OPTIONS
    public Response preflightedCorsTestCorsOptions() {
        return null;
    }


La guía que esta a continucación esta OBSOLETA.


Para añadir el soporte CORS a nuestros servicios REST, basta con añadir unas determinadas cabeceras a nuestra respuesta y modificar nuestro método actual para que las añada.

En primer lugar, el tipo de retorno de nuestro método, debe cambiar al tipo Response y deberemos introducir el objeto respuesta dentro de una clase del tipo GenericEntity. En el siguiente ejemplo mostramos cómo quedaría un método así:

import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Response;
  ...
  ...
  @GET
  @Path( "/metodo/{param1}/{param2}" )
  @Produces( MediaType.APPLICATION_JSON + ";charset=utf-8" )
  public Response corsEjemplo( @PathParam( "param1" ) String param1, @PathParam( "param2" ) String param2) {
	....
 
      GenericEntity entity = new GenericEntity<ClaseObjetoRespuesta>( objetoRespuesta ) {};
      return makeCORS( Response.ok( entity ) );
    }

El siguiente paso es crear un método para recuperar las opciones del recurso al que estamos accediendo. Este nuevo método se llamará como el anterior pero los parámetros y las anotaciones serán diferentes. A continuación tenéis un ejemplo

   private String corsHeaders;
 
   ...
 
    @OPTIONS
    @Produces( MediaType.APPLICATION_JSON + ";charset=utf-8" )
    @Path( "/metodo/{param1}/{param2}" )   
    public Response corsEjemplo( @HeaderParam( "Access-Control-Request-Headers" ) String requestH ) {
 
        corsHeaders = requestH;
        return makeCORS( Response.ok(), requestH );
 
    }

A continuación debemos implementar el método makeCORS que es el que añadirá las cabeceras necesarias. El código es el siguiente:

 
   private Response makeCORS( ResponseBuilder req ) {
        return makeCORS( req, corsHeaders );
    }
 
   private Response makeCORS( ResponseBuilder req, String returnMethod ) {
        ResponseBuilder rb = req.header( "Access-Control-Allow-Origin", "*" ).header( "Access-Control-Allow-Methods",
                                                                                      "GET, POST, OPTIONS" );
 
        if ( !"".equals( returnMethod ) ) {
            rb.header( "Access-Control-Allow-Headers", returnMethod );
        }
 
        return rb.build();
    }
 

  • fdw2.0/fundeweb2.0/gt/rest/soporte_cors_para_rest.txt
  • Última modificación: 09/07/2020 15:20
  • por JUAN MIGUEL BERNAL GONZALEZ