Desenvolvimento Web, Design

Exceções: “checked ou unchecked – Eis a questão!”

Você é designado para procurar um bug num sistema super importante onde você trabalha. Ao ler o código, você percebe um trecho assim:


try {
    controladorUsuario.salvar(diretor);
 }
 catch(Exception e) {}

E agora? Largo tudo, ligo pra minha mãe dizendo que a amo e me jogo da varanda? Calma, ainda não. Você, como bom programador deve consertar o código primeiro. Pensando em como as exceções são tratadas de forma desordenada nos sistemas legados que eu já trabalhei/já fiz, resolvi pesquisar mais sobre a forma correta de se tratar erros/exceções nos nossos  sistema. Há tempos já vinha pensando sobre o assunto e resolvi escrever no blog agora, como uma forma de difundir essas técnicas. Neste post, eu dou algumas dicas e conceitos sobre exceções que pude coletar em alguns livros de peso na área de desenvolvimento de Software. Aí vão elas:

Checked ou unchecked?

Primeiramente, vamos aprender/rever o conceito de exceções checked (checadas) e unchecked (não checadas) que servirão de base para alguns argumentos mais a frente neste post.

Checked:  São exceções que obrigatoriamente devem ser tratadas de alguma forma. Ou seja, o programador é obrigado a fazer algo com ela – tratar com um bloco try-catch (e suas combinações) ou relançar com throws na assinatura do método. Note que esse tipo de exceção não existe na maioria das linguagens. Por exemplo, Ruby e C# não possuem o conceito de checked exceptions.

Unchecked: São exceções que o programador não é obrigado a tratar.  Elas podem ser tratadas, mas o ponto é que isto não é obrigatório! Caso não sejam tratadas,  são automaticamente relançadas para a camada acima até achar alguém em alguma camada que a trate – em última instância, o erro é lançado na tela ou pior, estoura silenciosamente e pára. Em Java, essas exceções são subclasses de RuntimeException (exceções de tempo de execução).

Ok então. Mas, qual das duas usar? “Se a maioria das linguagens não tem exceções checadas, é porque não são realmente muito importantes” você talvez pense.  Esse é um assunto que dá muito pano pra manga e vários escritores técnicos bem reconhecidos já opinaram sobre isso. Alguns contra, outros a favor. Mas, gosto de ver pelo seguinte ponto de vista: Isso depende muito do que você quer. Pense por um momento no conceito de capturar exceções. É uma chance que o programador tem de se recuperar de uma falha na execução do programa. Quando você lança uma exceção checada, você está na verdade deixando bem explícito pra quem for cliente do seu código que ele tem sim uma chance de se recuperar da falha que ocorreu.  Por exemplo, ao tentar sacar R$50,00 numa conta com R$10,00 de saldo, você deve lançar algo do tipo SaldoInsuficienteException – checada. Dessa forma, não tem perigo de o programador deixar essa falha vazar para a camada acima e você o dá a chance de redirecionar o usuário para uma página de erro mais amigável. Agora, caso haja um incêndio no prédio onde o seu banco de dados está e nessa mesma hora o usuário tentou persistir um novo saldo, o que fazer? Bom, não tem como se recuperar, tem? Normalmente não.  Você não quer obrigar o programador a tratar um erro que não tem mais o que fazer, quer? Então lance uma exceção unchecked. São justamente essas 2 perguntas que você deve se fazer. “Existe uma chance de se recuperar da falha? ” e/ou “Quero dar uma chance de se recuperar dessa falha?”. É por isso que exceções como a famosa NullPointerException são não-checadas. Mesmo que você trate dentro do bloco catch para que o procedimento seja repetido ou algo assim, não vai mudar o fato que você está tentando acessar uma referência nula. Simplesmente ocorreu um erro de programação.

Cada um no seu quadrado

É importante respeitar o nível de abstração dos seus componentes. Tipo,  você já deve ter visto algo como (assuma que essa essa exceção é checked):


class TelaSalvarConta {

...

    try {

        conta.salvar();

   }

    catch(ConcurrentSerializationException exc) {...}

Aqui temos uma classe de alto nível manipulando um objeto de domínio e lançando uma exceção de baixíssimo nível. Isso é uma exposição indevida de seus objetos e significa que você quebrou o encapsulamento violentamente. Essa exceção deve ter vindo de há muito tempo sendo relançada pela própria API  até chegar a camada mais alta do sistema. Isso significa que a assinatura do método salvar() da classe Conta tem uma exceção (throws) com esse mesmo nome: ConcurrentSerializationException. Absurdo, não? Isso jamais devia ter acontecido. Em algum ponto, algum programador da API deveria ter feito:


catch(ConcurrentSerializationException exc) {

    throw new ObjectAlreadyInTransactionException(exc);

}

Sendo ObjectAlreadyInTransactionException uma exceção não-checada, não seria necessário o programador declarar uma exceção na assinatura do método, além disso, se ele desejar tratar essa exceção mesmo sem ser obrigado a isso, ele tem uma exceção mais próxima da camada de negócios.

Em resumo, trate cada exceção de acordo com a camada do sistema a qual ela pertence.

Evite sujeiras

E por sujeira eu quero dizer exceções checadas. Por exemplo, você gosta da forma como a API IO de Java trata as exceções? Todas elas são checadas! Muitas vezes eu quero fazer algum teste bem bobo no qual eu preciso ler um arquivo ou um properties e adivinha… sim, preciso de um try-catch pra um erro que não há recuperação.  Lembre-se que o tratamento de exceções por meio de try-catch impossibilita melhoria de performance no seu código pela JVM. Isso é muito importante. Sem contar que deixa seu código sujo e “fedido”. Ou então você precisa de mais uma exceção declarada na assinatura do método, o que quebra o encapsulamento (dando dicas pra o cliente do código o que aquele método faz) e dificulta a flexibilidade da API impondo regras a quem quiser herdar aquele método.  Faça o máximo para sempre lançar exceções não checadas, exceto (como já falado) se existir uma forma de se recuperar do erro e você quiser realmente obrigar o cliente a fazer isso. Um aspecto em que você sai ganhando é que em exceções não checadas você pode simplesmente deixar que elas cheguem a parte mais “alta” da aplicação e tratá-las de forma centralizada. Por exemplo, num ambiente web com filtros.  Senão utilizando algum framework de AOP.

Os erros são amigos

É de partir o coração ver algumas pessoas fazerem o seguinte ao tratar erros:


catch(Exception e) {}

ou


catch(Exception e) {

throw new Exception("Erro!");

}

Esses dois exemplos mostram código terrível e jamais devem ser usados. No primeiro o programador simplesmente ignora o erro e este se perde no limbo. Se você for forçado a tratar o erro no seu código, você deve tentar ao menos logar a exceção. Jamais ignore-a completamente como no primeiro caso.  Uma boa abordagem é tentar relançar a exceção em forma de RuntimeException, dando um throw new RuntimeException(e) – ou qualquer outra exceção do tipo RuntimeException que se adeque semanticamente ao erro. Dessa forma, você pára de impor que as camadas acima tenham de tratar ou declarar a exceção. Repito, jamais ignore exceções no catch.

O segundo exemplo é melhor que o primeiro mas ainda assim não é o ideal. Se um outro desenvolvedor está testando sua aplicação e se depara com uma mensagem de exceção dessa? Tipo… o que aconteceu que deu erro? Pois é, você pode ser mais específico e criar uma exceção mais específica para o erro, assim também como uma mensagem mais explicativa. Lembre sempre disso: “Programe sempre como se o cara que fosse  manter seu código fosse um psicopata violento que sabe onde você mora”.

Conclusão

Mostrei aqui algumas dicas de boas práticas sobre como lidar com exceções. Não é nada muito difícil, realmente. Apenas exige esforço constante do desenvolvedor para que o código seja facilmente compreendido. Pense sempre se deve realmente usar uma exceção checada. Na maioria dos casos uma exceção não checada dá conta do recado de forma mais sucinta. Não misture as abstrações das exceções. Exceções de baixo nível em baixo nível, as de alto nível em alto nível. E sempre que for tratar uma exceção, verifique se está tratando no nível correto de abstração. Se estiver, utilize mensagens explicativas sobre o erro ocorrido. E caso encontre aberrações (como a mencionada no começo do post), não se jogue da varanda. Respire fundo e comece a aplicar o que aprendeu.

Anúncios
Padrão
Desenvolvimento Web

Filtro Anti-XSS em Java

Há um bom tempo, na empresa onde trabalho, enfrentei uma situação curiosa. Eu ainda estagiava e um colega (hoje na globo.com) me perguntou: “quão segura é a aplicação que você está desenvolvendo?”. Eu, muito ingênuo (bem, muito mais do que hoje), respondi que era possível encontrar bugs, mas nada catastrófico a ponto de pôr tudo a perder. Outro colega (talvez mais ingênuo) teve a audácia de dizer que o sistema sob sua responsabilidade era livre de erros de segurança de qualquer tipo. A questão voltou-se completamente contra o programador super-confiante. Todos começaram a pensar em formas de encontrar bugs catastróficos e logo, o primeiro colega, autor da questão inicial, encontrou a brecha. Em poucos minutos, ele mostrou como cadastrar um item num dos cadastros do sistema para que, quando alguém clicasse nesse item, o sistema direcionaria o usuário para o site do google (ele foi bonzinho, poderia direcionar para algo bem pior…). Não preciso mencionar que o responsável pelo sistema ficou sem fala… Logo após, ele mencionou como era fácil encontrar sites expostos a este tipo de ataque e mostrou uma meia dúzia deles. Nos próximos dias comecei a trocar umas idéias com alguns desenvolvedores sobre como defender-se de ataques XSS. Esse post mostra basicamente uma forma de defesa “server-side” contra esse tipo de ataque (mais especificamente em sistemas Java, mas obviamente, a idéia se aplica a muitas outras linguagens).

Primeiro, uma tentativa de definição do que é Cross-Site Scripting:

Cross-Site Scripting (XSS) é um tipo de vulnerabilidade que pode existir em aplicações Web, na qual o ataque começa no lado do cliente, “atravessa” o sistema e permite ao atacante obter informações não permitidas de outros usuários que acessam ou acessaram a aplicação. Isso é feito por meio de injeção de código malicioso dentro das páginas web acessadas pelos usuários.

Ataque XSS

Ataque XSS

Esses ataques normalmente são códigos javascript inseridos por meio de campos de texto da aplicação e é bem fácil de simular. Aqui você encontra como simular um ataque XSS. É bem simples mesmo. Da mesma forma, a solução que eu utilizei é bem simples, é server-side e aqui vai um passo-a-passo de como implementar isso:

1 – A idéia é construir uma classe para tratar a requisição (request) da forma como você deseja. No meu caso, precisávamos tratar requests com tags html, como por exemplo “<script>” e “<a href>”. Um método estático que recebe a String original e retorna uma String modificada (do jeito que você precisa fazer) deve dar conta do serviço. Para casos mais gerais – e acredito que tratar TODAS as tags do tipo “<*>” – aconselho usar expressões regulares.

– Exemplo de classe TratarTags:


public class TratarTags {

    /*Expressão representando todas as expressões com tags html. "?"
    significa não-gulosa(non-greedy).*/
    private static final String expressaoComTags = "<.*?>";

    public static String retirarTags(String html) {

        String retorno = html.replaceAll(expressaoComTags, "");
        return retorno;
    }
}

2 – Construir o filtro propriamente dito. É necessário que você implemente a interface javax.servlet.Filter que consiste dos métodos init, destroy e – chegaremos lá – doFilter. Dentro do init e do destroy não tem muito o que fazer. O Container Web (uso o Tomcat 6.0.14 no momento) é esperto o suficiente pra saber o que fazer com esses caras. Particularmente falando, coloquei um log pra sinalizar inicialização e destruição do filtro.

Dentro do doFilter() – e é agora que começa a ação – você deve pegar o objeto do tipo HttpServletRequest, decorá-lo dentro de um tipo que você criará (ver passo 3) e passá-lo adiante para o container. Daí em diante, o container decidirá se enviará o request embrulhado, ou seja, seu objeto “request” criado no passo 3, para o próximo filtro (se existir um) ou para o servlet em questão. Para passá-lo adiante é bem simples. Pegue o objeto FilterChain que foi passado por parâmetro e chame o método doFilter do mesmo passando o request embrulhado e o response (você também pode alterá-lo se quiser! =D).

3 – Para entender esse passo você precisa ter ao menos uma noção do que é um Decorator. Tá. É um padrão de projeto, eu sei. Quase todo mundo sabe. O que eu quero dizer é que você precisa ENTENDÊ-LO.

A má notícia é que você não pode alterar o request em si e passá-lo adiante. Por outro lado, ninguém nunca falou nada sobre passar um request “de mentirinha” para o container. É isso que você faz basicamente. Você cria um objeto wrapper, no caso um RequestWrapper, que faça tudo que um HTTPServletRequest normal faz. “Tenho que implementar a interface de HttpServletRequest então…” você pensa. Você até pode. Mas essa interface possui uma monstruosidade de métodos que, acredite em mim, você não vai querer implementar um a um. Por isso já existe na API Java Servlet uma classe chamada HttpServletRequestWrapper, a qual você só modifica os métodos que você quiser! É só estendê-la! Agora ficou fácil. Bom, pra tratar tags html eu sobrecarreguei todos os métodos getParameter* para pegar o valor do getParameter* original, aplicar a regra da minha classe TratarTags (passo 1) e retornar o valor modificado. Sempre que uma requisição chegar ao servlet e você quiser extrair um valor do request(um parâmetro por exemplo), esse valor já chegará SEM as tags. O que significa que o filtro deu conta do trabalho.

Exemplo: TratarTagsFilter:

public class TratarTagsFilter implements Filter {
    private MapeamentoUrlCampo mapeamento = null;

    public TratarTagsFilter() {
        this.mapeamento = MapeamentoUrlCampo.getMapeamento();
        this.mapeamento.addMapeamento("campanhaAction.do", "script");
     }

     private static final Log logging =
     LogFactory.getLog(TratarTagsFilter.class);

     public void destroy() {
         logging.info("Terminando de executar o filtro. Classe= " + this);
     }

     public void doFilter(ServletRequest request,
     ServletResponse response, FilterChain chain) throws IOException,
     ServletException {
         logging.info("Executando o filtro neste momento. Classe= " + this);
         RequestWrapper requestWrapper = new RequestWrapper(request);
         chain.doFilter(requestWrapper, response);
     }

         public void init(FilterConfig arg0) throws ServletException {
         logging.info("Iniciando o filtro neste momento. Classe= " + this);
     }
     /*** Classe Interna estática que adiciona uma nova
     funcionalidade aos métodos da Classe HttpServletRequest* */
     private static class RequestWrapper extends HttpServletRequestWrapper {

         public RequestWrapper(ServletRequest request) {
             super((HttpServletRequest)request);
         }

         @Override
         public String getParameter(String name) {
             String valor = super.getParameter(name);
             String urlAtual = this.getRequestURL().toString();
             MapeamentoUrlCampo mapeamento = MapeamentoUrlCampo.getMapeamento();
             if(valor != null && !mapeamento.permiteCampoHtml(urlAtual, name)){
                 String novoNome = TratarTags.retirarTags(valor);
                 return novoNome;
             }
             else {
                 return valor;
             }
         }
         @Override
         public Map getParameterMap() {
             Map<String, String> map = super.getParameterMap();
             Set<String> chaves = map.keySet();
             List<String> listaChaves = new ArrayList<String>(chaves);
             Map<String, String> mapRetorno = new TreeMap<String, String>();
             for(String chave : listaChaves) {
                 mapRetorno.put(chave, this.getParameter(chave));
             }
             return mapRetorno;
         }
         @Override
         public String[] getParameterValues(String name) {
             String[] valores = super.getParameterValues(name);
             String[] novosValores;
             String urlAtual = this.getRequestURL().toString();
             MapeamentoUrlCampo mapeamento = MapeamentoUrlCampo.getMapeamento();
             if(valores != null && !mapeamento.permiteCampoHtml(urlAtual, name)) {
                 novosValores = new String[valores.length];
                 for(int i = 0; i < valores.length; i++) {
                     novosValores[i] = TratarTags.retirarTags(valores[i]);
                 }
                 return novosValores;
             }
             else {
                 return valores;
             }

         }
    }

}

4 – Agora basta configurar o filtro construído no seu web.xml. Dentro do elemento web-app você cria
duas tags:

A primeira é:


<filter>
    <filter-name>TratarTagsFilter</filter-name>
    <filter-class>com.projeto.filtro.TratarTagsFilter</filter-class>
</filter>

Que você direciona para interceptar apenas um padrão específico de url dessa forma:


<filter-mapping>
    <filter-name>TratarTagsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Pronto, o filtro está pronto pra funcionar na sua aplicação. Não sei se existe outra estratégia de evitar ataques via XSS fora essa de retirar tags html arriscadas. Sei que essa funciona eficientemente, por isso decidi utilizá-la. No entanto, se algum leitor souber de outra abordagem interessante, ficaria muito curioso em ouvi-la.

Padrão