· Vladyslav Baidak · Backend Development  · 3 min de leitura

Desenvolvimento Backend: Ativar o CORS (JAX-RS)



Desenvolvimento Backend: Ativar o CORS (JAX-RS)

Uma das dificuldades comuns que as equipas de desenvolvimento backend podem enfrentar ao criar serviços RESTful é o Cross-origin resource sharing (CORS). Uma vez que o JAX-RS não permite CORS por predefinição, vamos implementar uma solução que o ativa.

Ao alojar os recursos back-end e front-end na mesma máquina (por exemplo, localhost) não haverá problema, mas quando se trata de uma implementação cross-domain (por exemplo, quando os recursos front-end estão alojados numa máquina diferente) é necessária uma solução adequada.

Implementar a funcionalidade CORS

Para ativar o CORS como uma funcionalidade no JAX-RS, precisamos de adicionar a seguinte dependência RestEasy JAX-RS:

<dependency>
  <groupId>org.jboss.resteasy</groupId>
  <artifactId>resteasy-jaxrs</artifactId>
  <version>3.1.0.Final</version>
</dependency>

Esta fornece um CorsFilter conveniente, que podemos adicionar como uma funcionalidade:

import javax.ws.rs.core.FeatureContext;
import org.jboss.resteasy.plugins.interceptors.CorsFilter;

@Provider
public class CorsProvider implements Feature {

    @Override
    public boolean configure(FeatureContext context) {
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.getAllowedOrigins().add("*");
        context.register(corsFilter);
        return true;
    }
});

Como pode reparar, estamos a adicionar * às origens permitidas. Isto irá adicionar todos os cabeçalhos CORS apropriados a qualquer pedido de saída.

Implementar o filtro CORS

Se não quiser adicionar dependências adicionais para um caso tão simples, pode implementar o filtro CORS da seguinte forma:

@PreMatching
public class CorsFilter implements ContainerRequestFilter, ContainerResponseFilter {

    private static final String HEADER_ORIGIN = "Origin";

    private static final String HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";

    private static final String HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";

    private static final String HEADER_ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";

    private static final String HEADER_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";

    private static final String HEADER_ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";

    private static final String HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";

    private Set<String> allowedOrigins = new HashSet<>();

    public Set<String> getAllowedOrigins() {
        return allowedOrigins;
    }

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String origin = requestContext.getHeaderString(HEADER_ORIGIN);
        if (origin == null) {
            return;
        }
        if (requestContext.getMethod().equalsIgnoreCase("OPTIONS")) {
            preFlight(origin, requestContext);
        } else {
            checkOrigin(requestContext, origin);
        }
    }

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        String origin = requestContext.getHeaderString(HEADER_ORIGIN);
        if (origin == null
                || requestContext.getMethod().equalsIgnoreCase("OPTIONS")
                || requestContext.getProperty("cors.failure") != null) {
            return;
        }
        responseContext.getHeaders().putSingle(HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, origin);
        responseContext.getHeaders().putSingle(HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
    }


    private void preFlight(String origin, ContainerRequestContext requestContext) throws IOException {
        checkOrigin(requestContext, origin);
        Response.ResponseBuilder builder = Response.ok();
        builder.header(HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, origin);
        builder.header(HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");

        String requestMethods = requestContext.getHeaderString(HEADER_ACCESS_CONTROL_REQUEST_METHOD);
        if (requestMethods != null) {
            builder.header(HEADER_ACCESS_CONTROL_ALLOW_METHODS, requestMethods);
        }

        String allowHeaders = requestContext.getHeaderString(HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
        if (allowHeaders != null) {
            builder.header(HEADER_ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders);
        }
        requestContext.abortWith(builder.build());
    }

    private void checkOrigin(ContainerRequestContext requestContext, String origin) {
        if (!allowedOrigins.contains("*")
                && !allowedOrigins.contains(origin)) {
            requestContext.setProperty("cors.failure", true);
            throw new ForbiddenException("Origin not allowed: " + origin);
        }
    }
}

Esta é quase a mesma implementação que org.jboss.resteasy.plugins.interceptors.CorsFilter, mas sem a necessidade de importar dependências adicionais.

Permitir o método HTTP OPTIONS

A última coisa a ter em conta é permitir que o método HTTP OPTIONS prossiga. O método HTTP OPTIONS é normalmente utilizado pelos browsers antes de enviar o pedido CORS real.

Se tiver restrições de segurança configuradas no ficheiro descritor web.xml, precisa de adicionar o método HTTP OPTIONS utilizando a seguinte declaração de lista de permissões:

<security-constraint>
  <web-resource-collection>
    <url-pattern>/*</url-pattern>
    <http-method>OPTIONS</http-method>
  </web-resource-collection>
</security-constraint>

Tenha um bom dia e leia mais artigos sobre desenvolvimento backend aqui!

Back to Blog

Related Posts

View All Posts »