====== 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//. com.sun.jersey.spi.container.ResourceFilters es.um.atica.jersey.cors.ResourceCorsFilterFactory 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 ([[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|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: - GET como método permitido para CORS. - //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 [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|preflight]] o autorización previa, donde en la anotación //@CrossDomain// indicamos: - GET y OPTIONS como métodos permitidos para CORS. - //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 [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|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 [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|preflight]] o autorización previa, donde tenemos varios métodos con el mismo valor de //@Path//. En este caso para gestionar bien el [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|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 [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|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 [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|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 [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|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 [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|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; } \\ ===== Bibliografia y Referencias ===== * [[https://developer.mozilla.org/es/docs/Web/HTTP/Access_control_CORS|Control de acceso HTTP (CORS)]] * [[https://www.moesif.com/blog/technical/cors/Authoritative-Guide-to-CORS-Cross-Origin-Resource-Sharing-for-REST-APIs/|Authoritative guide to CORS (Cross-Origin Resource Sharing) for REST APIs]] * [[https://github.com/apache/tomcat/blob/5be3ec053432d7c1f42baf2e163339588b370b9e/java/org/apache/catalina/filters/CorsFilter.java|Apache Tomcat CorsFilter.java]] * [[https://www.javacodegeeks.com/2018/09/really-know-cors.html|Do You Really Know CORS?]] * [[https://developer.mozilla.org/es/docs/Glossary/Preflight_peticion|Mozilla Glossary - CORS Preflight]] ---- --- //[[juanmiguel.bernal@ticarum.es|JUAN MIGUEL BERNAL GONZALEZ]] 09/07/2020 13:20// ---- ===== Guía Anterior ===== 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( 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(); } ---- * [[https://www.javacodegeeks.com/2018/09/really-know-cors.html|Do You Really Know CORS?]]