Desenvolvimento seguro com Apache Shiro – Parte 1

Este artigo apresenta o Apache Shiro, um framework de segurança mantido pela Apache como um de seus “Top Level Projects”. Esse framework pode ser usado para implementar segurança nas mais diversas plataformas Java, diferente do Spring Security.

Artigo do tipo Exemplos Práticos
Recursos especiais neste artigo:
Artigo no estilo Curso Online.
Conhecendo o Apache Shiro – Parte 1
Este artigo apresenta o Apache Shiro, um framework de segurança mantido pela Apache como um de seus “Top Level Projects”. Esse framework pode ser usado para implementar segurança nas mais diversas plataformas Java, diferente do Spring Security, que funciona apenas em aplicações web.

Em que situação o tema é útil
Este tema é útil a todos os desenvolvedores que precisam implementar segurança nas mais diversas aplicações Java (não só na plataforma Web) e que não querem ter complicações para isso. O Apache Shiro torna essa implementação tão fácil e intuitiva quanto possível, visto que esse é o seu objetivo principal.

Em 2003, a realidade das soluções de segurança para a plataforma Java não era das melhores. As poucas opções existentes não possuíam todas as funcionalidades necessárias e eram difíceis de usar. Uma das únicas soluções era o Java Authentication and Authorization Service (JAAS), que não supria bem o controle de segurança de uma aplicação. Nesse cenário, nasceu o JSecurity, predecessor do Apache Shiro. Desde o início, o principal objetivo do projeto sempre foi a facilidade de uso, para acabar com as frustrações dos desenvolvedores no desenvolvimento da segurança das aplicações.

Depois de anos de projeto e uma comunidade crescente, ele passou para a incubadora de projetos da Apache, mudando seu nome para Ki. Em 2010 se tornou um dos chamados “Top Level Projects” da Apache, assim como Tomcat, Cassandra e tantos outros. Ao alcançar esse nível, o projeto foi renomeado para Shiro, uma palavra japonesa que significa Castelo. Essa promoção ajudou a manter o crescimento da comunidade de desenvolvedores e utilizadores.

Se olharmos para o Apache Shiro desde o seu nascimento, quando ainda era chamado JSecurity, veremos que seu principal objetivo sempre foi a simplicidade e a facilidade de uso. Pode-se dizer que esse objetivo é bem cumprido, pois o embasamento conceitual é bastante intuitivo e ele possui uma boa API para utilização.

No entanto, antes de partirmos para os conceitos usados em sua implementação, é importante conhecermos o escopo do projeto. O núcleo do Apache Shiro tem interesse em resolver os seguintes pontos: Autenticação, Autorização, Criptografia e gerenciamento de sessão do usuário independente de plataforma. Além disso, ele também se preocupa com funcionalidades auxiliares e de integração com diversas plataformas. Entre essas funcionalidades, estão: suporte a aplicações Web (filtragem de URLs, por exemplo), integração com bibliotecas de cache para evitar o lento acesso às informações necessárias para a segurança, funcionalidade de “remember me”, possibilidade de um usuário assumir temporariamente a identidade de outro (o que pode ser útil em casos de suporte ao cliente), entre outros. Um resumo da estrutura do Apache Shiro pode ser visto na Figura 1, retirada da documentação do projeto.

Figura 1. Visão das principais funcionalidades do Apache Shiro.

Com o escopo do projeto em mente, podemos nos concentrar nos conceitos de segurança empregados. Para começar, veremos as principais funcionalidades: Autenticação e Autorização.

Subject e Autenticação

A segurança, no Apache Shiro, é centralizada na entidade Subject. Essa nada mais é do que o sujeito que está operando o sistema no momento. Ao invés de se chamar User, eles preferiram a palavra Subject, pois ela pode significar qualquer coisa que esteja interagindo com o sistema. Muitas vezes, esse sujeito será um usuário comum, mas também pode ser outro sistema interagindo com a aplicação protegida, por exemplo.

Para obter essa entidade, o Apache Shiro expõe uma API de fácil uso. O código necessário para essa operação é mostrado na Listagem 1. Vale destacar que esse código sempre retornará o Subject corrente da aplicação, independente de onde for usado.

Listagem 1. Obtendo uma instância do Subject corrente.

Subject subject = SecurityUtils.getSubject();

Quando esse método é chamado pela primeira vez, antes da autenticação, ele retornará uma instância considerada anônima. Depois que o usuário é autenticado, esse mesmo código mostrado na Listagem 1 retornará o Subject considerado autenticado. Assim, esse método jamais retornará null, mas sim uma instância com estado diferente.

Como foi dito antes, a API do Apache Shiro é centralizada nessa entidade. Portanto, para que possamos autenticar o usuário, usamos o método login() do Subject. Como parâmetro, passamos um objeto com a identificação e as credenciais fornecidas pelo sujeito que está tentando autenticar na aplicação. Na Listagem 2, podemos ver a forma mais comum de credenciais fornecidas: nome de usuário e senha.

Listagem 2. Autenticando um usuário através de username e password. AuthenticationToken token = new UsernamePasswordToken(username, password); try{ SecurityUtils.getSubject().login(token); } catch(UnknownAccountException e){ } catch(DisabledAccountException e){ } catch(IncorrectCredentialsException e){ } catch(AuthenticationException e){ }

O objeto passado como parâmetro para o método login() deve ser um implementador da interface AuthenticationToken. Essa interface possui dois métodos a serem implementados: getPrincipal() e getCredentials(). Esses métodos simplesmente retornam dois atributos: principal e credentials. Em uma aplicação comum, o principal será o nome do usuário (login) e as credentials (ou credenciais) representarão a senha do mesmo. Assim, podemos entender que o principal é algo que identifica unicamente um usuário no sistema e as credenciais provam que o usuário é quem ele diz ser.

Foi decidido pela criação dessa interface por que nem todos os sistemas usarão apenas login e senha para autenticar usuários. Existem casos em que o usuário poderá ser autenticado através de um web service via um token de autorização, por exemplo, que é o caso do padrão conhecido como OAuth. Para os usos mais simples em que o usuário é autenticado por seu login e senha, já existe uma implementação dessa interface: UsernamePasswordToken. Em nosso exemplo, criamos uma instância dessa classe simplesmente passando o username e password do usuário.

Também notamos, na Listagem 2, o que acontece numa falha de autenticação. Podemos observar que várias exceções podem ser lançadas em uma falha de autenticação, sendo todas elas subclasses de AuthenticationException. Cada subclasse identifica precisamente o que causou o erro de autenticação. Elas vão desde usuário desconhecido até senha incorreta, além de outras. Além disso, também poderemos implementar nossas próprias exceções que serão lançadas no processo de autenticação.

Dica de segurança: evite dar informações detalhadas sobre o porquê de a autenticação ter falhado, pois isso pode ajudar um hacker a acessar uma conta que não o pertence.

Quando o método login() é chamado, o Apache Shiro coordena uma série de ações para verificar a autenticidade do token submetido para autenticação. Tudo isso é controlado pelo SecurityManager, que é responsável por gerenciar todas as operações de todos os sujeitos. Mais detalhes sobre essa classe serão explicados em breve neste artigo. Antes de entrar nesses detalhes, é importante saber sobre o que controla o que determinado sujeito pode fazer na aplicação.

Autorização

Saber que um sujeito é quem ele diz ser não é suficiente para tornar nossa aplicação segura. Também devemos saber o que ele pode e o que não pode fazer na aplicação. Afinal, não é qualquer usuário do sistema que poderá excluir outros usuários ou ver informações confidenciais, por exemplo. Nesse contexto, entra o conceito de Autorização.

Esse conceito é o que define as permissões de um sujeito no sistema. Essas permissões podem ser dadas de acordo com o papel realizado por ele. Para deixar mais claro, considere um sistema fictício de um Banco. Nele teríamos os papéis: gerente de contas, cliente, operador do caixa convencional, entre outros. Cada um dos usuários que exigem esses papéis possui determinadas permissões no sistema. O cliente não pode fechar diretamente sua conta, por exemplo. Apenas o gerente de contas possui permissão para realizar essa operação."

[...] continue lendo...

Artigos relacionados