Arquivo

Posts Tagged ‘resteasy’

Implementação de um Cliente RESTEasy

Escrevi um artigo para demonstrar como criar um mecanismo de autorização com token no RESTEasy. Fiz a implementação do lado do servidor e testei com o SOAPUI, mas nesse artigo vou demonstrar a implementação de um cliente Java que utiliza aqueles serviços que foram disponibilizados.

Primeiro, vamos criar as entidades do nosso modelo utilizando as anotações do Jackson. Você poderia trabalhar direto com String ou manipular o JSON manualmente, mas há boas bibliotecas para fazer essa conversão automaticamente. Vamos criar uma classe para representar a autenticação que será enviada ao método específico do endpoint e outra para representar a autorização, que é a resposta do endpoint que contém o token de autorização que deverá ser utilizado nas chamadas aos demais web services.

@JsonIgnoreProperties(ignoreUnknown = true)
public class Autenticacao {
	@JsonProperty("usuario")
	private String usuario;
	@JsonProperty("senha")
	private String senha;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public class Autorizacao {
   @JsonProperty("token")
   private String token;
}

O primeiro método que criaremos é aquele que consome a autenticação e produz uma instância da classe Autorizacao a partir do JSON retornado pelo web service. Nosso endpoint responde no endereço http://localhost:8080/endpoint e faremos uma chamada POST para enviar os dados da autenticação no cabeçalho da request.

   public Autorizacao autenticar() throws Exception  {
      Autenticacao autenticacao = new Autenticacao();
      autenticacao.setUsuario("usuario");
      autenticacao.setSenha("senha");
      Entity<Autenticacao> entity = 
         Entity.entity(autenticacao, MediaType.APPLICATION_JSON);
      ResteasyClient client = new ResteasyClientBuilder().build();
      ResteasyWebTarget target = client.
         target("http://localhost:8080/endpoint").
         path("/autenticar/");
      Response response = target.request(MediaType.APPLICATION_JSON).
         post(entity);
      String value = response.readEntity(String.class);
      response.close();
      ObjectMapper map = new ObjectMapper();
      return map.readValue(value, Autorizacao.class);
   }

O header da request ficará mais ou menos assim:

POST http://localhost:8080/endpoint/autenticar/ HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/json
Content-Length: 51
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.7)
{
	"usuario" : "admin",
	"senha" : "1234"
}

A resposta do web service será assim:

{
   "token": "81FE2DFA7A9A822D5EBA0A7B1ABA2CBDB7307B1D8A5869CDBEB64B47DA453AE3"
}

Agora que obtivemos um token que autoriza a utilização dos web services durante “T” unidades de tempo supondo que essa validade esteja implementada no servidor, vamos listar os usuários disponíveis com uma chamada GET cujo cabeçalho conterá um atributo “Token”.

@JsonIgnoreProperties(ignoreUnknown = true)
public class Usuario {
	@JsonProperty("nome")
	private String nome;
}
   public List<Usuario> listarUsuarios(Autorizacao autorizacao)
      throws Exception {
      ResteasyClient client = new ResteasyClientBuilder().build();
      ResteasyWebTarget target = client.
         target("http://localhost:8080/endpoint").
         path("/usuario/listar/");
      Response response = 
         target.request(MediaType.APPLICATION_JSON).
         header("Token", autorizacao.getToken()).get();
      String value = response.readEntity(String.class);
      response.close();
      ObjectMapper map = new ObjectMapper();
      Map<String, List<Usuario>> mapItens = map.readValue(value,
         new TypeReference<Map<String, List<Usuario>>>() {
      });
      return mapItens.get("usuarios");
   }

O header da request ficará assim.

GET http://localhost:8080/endpoint/usuario/listar/ HTTP/1.1
Accept-Encoding: gzip,deflate
Token: 81FE2DFA7A9A822D5EBA0A7B1ABA2CBDB7307B1D8A5869CDBEB64B47DA453AE3
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.7)

A resposta do web service será assim:

{
   "usuarios": [   
      { "nome": "José da Silva" },
      { "nome": "Mário de Souza" }
   ]
}

Por fim, vamos testar nosso cliente.

@Test
public void listarUsuarios throws Exception {
   WSClient client = new WSClient();
   Autorizacao autorizacao = client.autenticar();
   List<Usuario> usuarios = client.listarUsuarios(autorizacao);
   usuarios.stream().forEach((u) -> { System.out.println(u.getNome()); });
}

Referências

1. [https://atitudereflexiva.wordpress.com/2016/10/03/implementacao-de-autorizacao-com-token-utilizando-resteasy/]
2. [https://examples.javacodegeeks.com/enterprise-java/rest/resteasy/json-example-with-resteasy-jackson/]
3. [http://stackoverflow.com/questions/22095289/basic-authentication-with-resteasy-client]
4. [http://stackoverflow.com/questions/33241363/how-send-list-as-query-param-using-resteasy-client]
5. [http://www.concretepage.com/webservices/resteasy-3/jax-rs-resteasy-3-form-to-post-applicationx-www-form-urlencoded-content-type]
6. [http://howtodoinjava.com/resteasy/jax-rs-2-0-resteasy-3-0-2-final-client-api-example/]
7. [http://stackoverflow.com/questions/18520654/how-do-i-send-json-to-an-external-api-using-resteasy]

Implementação de Autorização com Token Utilizando RESTEasy

Gosto bastante de desenvolver orientado a aspectos. A especificação JAX-RS possibilita implementar um mecanismo de autorização simples para seu web service REST. Um mecanismo de segurança simples ainda é melhor do que não ter segurança e a segurança deve ser definida como parte da arquitetura e não como uma funcionalidade.

Nesse exemplo, vamos mostrar como implementar um mecanismo de autorização baseado em token que é verificado em um interceptador do RESTEasy. Você poderia utilizar um ContainerRequestFilter ao invés do interceptor, mas você teria que utilizar a annotation @NameBinding em conjunto com a sua annotation para só então poder utilizá-la para anotar seu filtro.

1. Crie um enum que será utilizado para parametrizar sua anotação.

public enum Role {
   AUTHENTICATION_REQUIRED;
}

2. Crie uma anotação que utiliza a enum definida anteriormente para criar uma política de segurança. Essa anotação e aquela enum serão verificadas no interceptador. Você poderia utilizar outros métodos para identificar os recursos onde a política se aplicará, mas a combinação de enum e anotação é simples e elegante.

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface SecurityPolicy {
   Role[] value() default {};
}

3. Implemente um interceptador que verifica a anotação criada e a presença do atributo “TOKEN” no header da request. Note que além da anotação que criamos anteriormente (SecurityPolicy), estamos testando as anotações padrão PermitAll e DenyAll que poderiam estar anotando o recurso e são mais abrangentes que a nossa. Se tudo é permitido ou se tudo é negado, não faz sentido fazer uma verificação mais granular.

@Provider
@ServerInterceptor
public class SecurityInterceptor implements PreProcessInterceptor {
   @Override
   public ServerResponse preProcess(HttpRequest request, 
        ResourceMethod methodInvoked) 
      throws Failure, WebApplicationException {
      Method method = methodInvoked.getMethod();
      if (method.isAnnotationPresent(PermitAll.class)) {
         return null;
      }
      if (method.isAnnotationPresent(DenyAll.class)) {
         return new ServerResponse("Não pode acessar esse recurso",
         Status.UNAUTHORIZED.getStatusCode(), 
         new Headers<Object>());
      }
      if (method.isAnnotationPresent(SecurityPolicy.class)) {
         SecurityPolicy rolesAnnotation = 
           method.getAnnotation(SecurityPolicy.class);
         List<Role> roles = Arrays.asList(rolesAnnotation.value());
         if (roles.contains(Role.AUTHENTICATION_REQUIRED)) {
            HttpHeaders headers = request.getHttpHeaders();
            List<String> authorization = 
              headers.getRequestHeader("TOKEN");
            if(authorization != null && !authorization.isEmpty())
            {
               String token = authorization.get(0); 
               boolean autorizado = /*regras de autorizacao*/;
               if(autorizado){
                  // Deve-se retornar nulo para que o 
                  // processamento da request continue normalmente
                  return null;
               }
            }
            return new ServerResponse("Usuário não autorizado", 
              Status.FORBIDDEN.getStatusCode(),
              new Headers<Object>());
         }
      }
      // Deve-se retornar nulo para que o 
      // processamento da request continue normalmente
      return null;
   }
}

4. Crie um recurso e anote o método que exige a aplicação da política de segurança

@Path("/usuario")
public class UsuarioResource {
 
  @EJB
   private UsuarioService service;

   @GET
   @SecurityPolicy(Role.AUTHENTICATION_REQUIRED)
   @Path("/buscar")
   @Produces(MediaType.APPLICATION_JSON)
   public Response buscar(@PathParam("id") String id) {
      try {
         Usuario usuario = service.buscar(id);
         ResponseUsuario respUsuario = new ResponseUsuario();
         respUsuario.setNome(usuario.getNome());
         return Response.status(Response.Status.OK).
            entity(respUsuario).
            header("Content-type", "application/json; charset=utf-8").
            build();
      } catch (ServicoException e) {
          return Response.status(Response.Status.FORBIDDEN).
            entity("Nao foi possivel localizar o usuario.").
            header("Content-type", "application/json; charset=utf-8").
            build();
      }
      return Response.status(Response.Status.UNAUTHORIZED).
        entity("Usuário não autorizado.").
        header("Content-type", "application/json; charset=utf-8").
        build();
   }
}

Você poderia utilizar algum navegador para testar o funcionamento do seu web service, mas prefiro utilizar o SOAPUI. Se você tiver alguma problema relacionado a certificado do Java, dê uma olhada nesse artigo.

token

Figura 1 – Interface do SOAPUI

Note o parâmetro “TOKEN” que passamos no cabeçalho da request. O ideal é que o valor seja obtido do próprio web service, talvez acessando o banco de dados, e que este tenha um tempo de expiração que faça sentido para o negócio e para alguma política de segurança da informação de nível operacional.

Referências

1. [http://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey
2. [https://www.infoq.com/br/news/2012/10/autenticacao-rest
3. [http://blog.rivendel.com.br/2013/06/07/seguranca-em-apis-rest-parte-1/
4. [http://www.slideshare.net/stormpath
5. [http://www.developerscrappad.com/1814/java/java-ee/rest-jax-rs/java-ee-7-jax-rs-2-0-simple-rest-api-authentication-authorization-with-custom-http-header/
6. [http://stackoverflow.com/questions/26777083/best-practice-for-rest-token-based-authentication-with-jax-rs-and-jersey
7. [https://www.mkyong.com/webservices/jax-rs/get-http-header-in-jax-rs/
8. [http://howtodoinjava.com/resteasy/jax-rs-resteasy-basic-authentication-and-authorization-tutorial/
9. [http://howtodoinjava.com/resteasy/jax-rs-2-0-resteasy-3-0-2-final-security-tutorial/[

Introdução ao REST

REST (REpresentational State Transfer) é um estilo de comunicação utilizado em web services. Hoje, REST é mais utilizado do que SOAP (Simple Object Access Protocol). Simplicidade e menor custo de transmissão de informações tornam o estilo REST ideal para uso na Internet. O estilo SOAP exige que se tenha um programa servidor para fornecer dados e um programa cliente para consumir dados além de implicar em maior consumo de banda para transmissão dos dados. Quando web services utilizam arquitetura REST eles passam a ser chamados de RESTful. REST oferece as seguintes vantagens quando comparado ao SOAP:

  • Web services RESTful são suportados por muitas ferramentas e por servidores de nuvem.
  • Serviços SOAP são mais difíceis de escalar. Por isso, REST é o estilo arquitetural escolhido para serviços expostos via Internet, como Twitter e Facebook.
  • A curva de aprendizado é baixa. Desenvolvedores são capazes de assimilar e utilizar REST em suas aplicações mais rápido do que SOAP.
  • REST utiliza um formato de mensagem menor do que o de SOAP. SOAP utiliza xml para todas as mensagens, o que aumenta o tamanho da mensagem e diminui a eficiência do serviço – menor custo de processamento e resposta mais rápida.

Dois conceitos importante do mundo dos web services são endpoints e recursos. O endpoint é uma URI (Uniform Resource Identifier) endereçada de forma padrão e que responde a requisições web. Recurso é um web service especializado que responde em uma determinada URI relativa ao endpoint que tem por propósito expor uma parte dos metadados do domínio do endpoint.

Ex:

URL: [https://www.site.com.br/endpoint/usuario/buscar/123]

Endpoint: [https://www.site.com.br/endpoint]

URI do Recurso: [/usuario/buscar/123]

Essa forma de mapear recursos é uma boa prática. Lê-se essa chamada como “recurso /usuario/buscar/, retorne o usuário que tem o identificador ‘123’”. É óbvio que “/usuario/buscar/” responde como GET, pois a chamada está sendo feita via parâmetro.

Embora o Jersey seja a implementação de referência para o JAX-RS, vamos fazer um exemplo utilizando o RESTEasy, que é a implementação do JBoss e ele vai nos fornecer as dependências em tempo de execução.

Exemplo

1. Adicione as dependências do RESTEsy para o seu projeto. Estou utilizando a versão 2.3.7.Final porque ela vem com o JBoss 6.4.

<dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-jaxrs</artifactId>
   <version>2.3.7.Final</version>
   <scope>provided</scope>
</dependency>
<dependency>
   <groupId>org.json</groupId>
   <artifactId>json</artifactId>
   <version>20131018</version>
</dependency>

2. No arquivo jboss-deployment-structure.xml do seu WEB-INF, informe ao JBoss que ele deve fornecer o RESTEasy para a sua aplicação em runtime. Você poderia empacotar sua versão do RESTEasy junto com sua aplicação, mas o JBoss ignora porque ele tem a dele.

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.2" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<deployment>
   <dependencies>
      <module name="org.jboss.resteasy.resteasy-jaxrs"/>
   </dependencies>
</deployment>
</jboss-deployment-structure>

3. Vamos definir uma classe que representa os dados de um usuário que serão expostos pelo recurso.

@XmlRootElement
public class ResponseUsuario{
   private String nome;
   public String getNome(){
      return nome;
   }
   public void setNome(String nome){
      this.nome = nome;
   }
}

Essa classe é convertida para o JSON abaixo:

{
   "nome" : "José da Silva"
}

4. Vamos implementar o recurso “/usuario/buscar/{id}” relativo ao nosso endpoint. O método “buscar” recebe um “id” por parâmetro e retorna um JSON:

@Path("/usuario")
public class UsuarioResource {

   @EJB
   private UsuarioService service;

   @GET
   @Path("/buscar")
   @Produces(MediaType.APPLICATION_JSON)
   public Response buscar(@PathParam("id") String id) {
      try {
         Usuario usuario = service.buscar(id);
         ResponseUsuario respUsuario = new ResponseUsuario();
         respUsuario.setNome(usuario.getNome());
         return Response.status(Response.Status.OK).entity(respUsuario).
           header("Content-type", "application/json; charset=utf-8").
           build();
      } catch (ServicoException e) {
         return Response.status(Response.Status.FORBIDDEN).
           entity("Nao foi possivel localizar o usuario.").
           header("Content-type", "application/json; charset=utf-8").
           build();
      }
      return Response.status(Response.Status.FORBIDDEN).
         entity("Nao foi possivel localizar o usuario.").
         header("Content-type", "application/json; charset=utf-8").
         build();
      }
   }
}

5. Vamos definir um tratador de exceções padrão. Esse tratador intercepta, por exemplo, a tentativa de acesso a um recurso inexistente ou a uma chamada POST a um recurso que só suporta GET.

public class EndpointExceptionHandler 
   implements ExceptionMapper<Exception> {
   @Override
   public Response toResponse(Exception e) {
      return Response.serverError().entity(e.getMessage()).build();
   }
}

6. Por fim, vamos habilitar o servlet do RESTEasy e mapear as requisições para o “/”. Você poderia mapear o endpoint para qualquer outra localização supondo que o código do web service está acoplado ao código de outra aplicação, o que não é uma boa prática arquitetural.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
id="WebApp_ID" version="3.1">
<display-name>EndPoint do Portal do Contador</display-name>
   <context-param>
      <param-name>resteasy.providers</param-name>
      <param-value>br.com.handler.EndpointExceptionHandler</param-value>
   </context-param>
   <context-param>
      <param-name>resteasy.scan</param-name>
      <param-value>true</param-value>
   </context-param>
   <context-param>
      <param-name>resteasy.servlet.mapping.prefix</param-name>
      <param-value>/</param-value>
   </context-param>
   <listener>
      <listener-class>
         org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
      </listener-class>
   </listener>
   <servlet>
      <servlet-name>resteasy-servlet</servlet-name>
      <servlet-class>
         org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
      </servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>resteasy-servlet</servlet-name>
      <url-pattern>/*</url-pattern>
   </servlet-mapping>
</web-app>

Para testar, basta abrir o navegador e acessar a URL local:

teste

Referências

1. [http://resteasy.jboss.org/]
2. [https://jax-rs-spec.java.net]
3. [http://www.devmedia.com.br/resteasy-alem-da-especificacao/23309]
4. [https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm]
5. [http://www.devmedia.com.br/boas-praticas-com-web-services-restful/18020]
6. [https://www.w3.org/TR/wsdl20/#Endpoint]
7. [http://programmers.stackexchange.com/questions/138455/what-is-a-recommended-pattern-for-rest-endpoints-planning-for-foresighted-change]
8. [http://stackoverflow.com/questions/17662012/need-help-understanding-rest-api-endpoints]
9. [http://stackoverflow.com/questions/30580562/what-is-the-difference-between-resource-and-endpoint]
10. [http://www.eclipse.org/eclipselink/documentation/2.5/solutions/jpatoxml003.htm]
11. [http://stackoverflow.com/questions/24307106/how-to-annotate-a-list-using-xmlelement]
12. [http://stackoverflow.com/questions/12256221/automatic-xmlrootelement-wrapper-for-list]
13. [http://racksburg.com/choosing-an-http-status-code/]
14. [http://stackoverflow.com/questions/942951/rest-api-error-return-good-practices]
15. [http://searchsoa.techtarget.com/definition/REST]