terça-feira, 28 de julho de 2009

Java Authentication and Authorization Service (JAAS)

Pessoal.

Neste post irei falar rapidamente sobre o JAAS (Java Autentication and Authorization Service), o mecanismo padrão Java para autenticação e autorização de aplicações corporativas.

Antes de tudo, eu gostaria de separar dois conceitos bastante distintos: autenticação e autorização.

Informalmente, por autenticação entende-se como a capacidade de determinar quem é o utilizador do sistema. Isto normalmente é feito através de uma combinação de nome de usuário e senha, chave pública e chave estrangeira, assinatura digital, etc. Enfim, a autenticação é responsável apenas por saber quem é quem.

Quanto a autorização, podemos definir como, dado que sabemos quem é o utilizador, determinar que recursos, a partir das credenciais do utilizador, este pode ou não ter acesso.

Mas afinal, o que é o JAAS? JAAS é um conjunto de APIs Java, que adiciona as funcionalidades de autenticação e autorização Java, implementando a especificação PAM (Puggable Authentication Module). Ou seja, JAAS é uma implementação de referência, porém podem existir outras implementações que se seguirem a
especificação PAM, podem ser plugadas a outras aplicações/contêineres JEE.


Conceitos básicos:

JAAS é composto por um número bastante pequeno de classes/interfaces. É importante conhecê-las rapidamente antes de começar a utilizar a tecnologia. Basicamente JAAS é formado por: Principal, Role, Login Module, Authentication Realm e Login Config.

  • Principal - Interface que define um usuário do sistema, um Principal deve ter pelo menos um nome que o identifique;
  • Role - Classe que define credenciais de usuário (ex: administradores, auditores, usuários, etc);
  • Login Module - Interface que define o comportamento a ser implementado por classes que devam realizar autenticação. Implementações desta interface devem registrar no contexto de segurança as informações do Princial e das Roles associadas a ele;
  • Authentication Realm - Implementações de Login Modules, definidas pelo servidor de aplicação, que podem ser instanciadas e parametrizadas diretamente no servidor de aplicação para serem acessadas por quaisquer aplicações utilizando JNDI para isto. A utilização de realms é interessante pois desacopla a estratégia de autenticação da implementação da aplicação;
  • Login Config - Informação configurada no arquivo WEB-INF/web.xml da aplicação que define a forma como a aplicação irá apresentar a autenticação para os seus usuários. É possível escolher entre as seguintes possibilidades: none, basic, digest, certificate e form.
Falaremos mais um pouco sobre as diferentes opções para a configuração do login (login config):
  • None - Nesta opção, padrão, não é realizado nenhum tipo de autenticação;
  • Basic - Utilizando esta opção, o browser irá mostrar uma janela onde o utilizador deverá entrar com o seu login/senha que serão enviados ao login module para verificação dos dados informados;
  • Digest - Semelhante ao basic, também e exibido um formulário para digitação do login/senha, porém as informações são criptografadas de agosto com um algoritmo configurado no próprio arquivo WEB-INF/web.xml. Existem algumas implementações padrão, como SHA e MD5, porém os desenvolvedores podem definir seus próprios algoritmos de criptografia;
  • Certificate - Autenticação baseada em certificados públicos e privados, desta forma, não há a necessidade de solicitar informações aos usuários, já que o certificado traz toda a informação necessária para a autenticação;
  • Form - Autenticação realizada através de um formulário web definido pelos desenvolvedores do sistema (configuração mais comum para login).
Estudo de caso

Iremos aplicar JAAS em um projeto web existente. Para isto iremos utilizar o Netbeans juntamente com o servidor de aplicação Glassfish. Neste exemplo utilizaremos um JDBC Realm e Form como Login Config.

Primeiro passo, vamos configurar o nosso authentication realm. Para realizar esta tarefa no Glassfish (para este exemplo utilizamos a versão 2.1) é necessário entrar no console de administração do servidor de aplicações.

Normalmente a URL do console de administração é http://localhost:4848, login - admin, senha - adminadmin:


Para este nosso exemplo, iremos utilizar um JDBC realm. Para utilizar esta implementação é necessário antes de criar um authentication realm, realizar das tabelas que irão armazenar a informação de autenticação bem como a definição do datasource no servidor de aplicações.

O esquema de banco de dados a ser criado (no nosso caso, utilizaremos o MySQL como tecnologia de banco de dados) deve ser constituído por pelo menos duas tabelas, uma com a informação dos usuários e outra com a informação das credenciais associadas a estes usuários:

CREATE TABLE USUARIO (
LOGIN VARCHAR(50) NOT NULL,
SENHA VARCHAR(50) NOT NULL,
PRIMARY KEY (LOGIN)
)

CREATE TABLE GRUPO_USUARIO (
GRUPO VARCHAR(50) NOT NULL,
LOGIN VARCHAR(50) NOT NULL,
PRIMARY KEY (GRUPO, LOGIN),
FOREIGN KEY (LOGIN) REFERENCES USUARIO(LOGIN)
)

... no Servidor de Aplicação

A definição de um data source é feita diretamente no servidor de aplicações. Esta estratégia de separar definição/configuração de uso foi tomada como padrão para o desenvolvimento de aplicações JEE e é bastante interessante pois consegue desacoplar o código de negócio das aplicações dos recursos externos utilizados por este código.

No Glassfish um data source é definido em dois passos: criação de um connection pool e criação de um data source propriamente dito. No primeiro passo são definidos os atributos da conexão com o banco de dados (classe do driver, url da conexão, usuário, senha, etc) bem como a definição de uma connection pool (número de conexões mínimo, número de conexões máximo, timeout de inatividade para conexões, etc) que intermediará o acesso entre a aplicação e o banco de dados.

Para criar uma connection pool no Glassfish devemos selecionar a opção Resources -> JDBC -> Connection Pools, em seguida devemos clicar no botão "New ...". Esta definição é feita em dois passos.

Passo 1 (definição do nome, classe do connection pool e tecnologia de banco de dados):


Entre com os valores e pressione o botão "Next".

Passo 2 (é mostrado uma enormidade de propriedades de configuração, 182, porém apenas é necessário mudar o valor das propriedades Url/URL, User e Password):

Em seguida, clique no botão "Finish".

Com o connection pool criado, vamos agora definir um data source, para isto devemos selecionar a opção Resources -> JDBC -> JDBC Resources, em seguida devemos clicar no botão "New ...".

Em seguida, precisamos apenas informar um nome JNDI (no formato: jdbc/nome_data_source) para o data source, selecionar o respectivo connection pool, definir uma breve descrição (opcional) e clicar no botão "Ok":



Agora iremos adicionar um authentication realm. Para isto devemos selecionar a opção Configuration -> Security -> Realms, em seguinda devemos clicar no botão "New ...". No formulário que será exibido, devemos entrar com os dados para a criação do realm (nome, implementação e propriedades relativas à implementação do realm):


É importante falar sobre algumas das propriedades informadas (lembre-se que estamos utilizando o JDBC e por isso precisamos escolher esta op
ção no ¨Class Name¨:
  • JNDI: Nome JDNI do data source criado anteriormente;
  • User table: Nome da tabela existente do data source informado que contém a informação dos usuários;
  • User name column: Nome da coluna na tabela de usuários que contém a informação do login para o usuário;
  • Password column: Nome da coluna na tabela de usuários que contém a informação da senha para o usuário;
  • Group table: Nome da tabela que contém as informações das credencias associadas aos usuários. Esta tabela deve estar definida no data source informado;
  • Group name column: Nome da coluna na tabela de grupos que contém a informação da credencial do usuário. Observação: Não é pedido para informar o nome da chave estrangeira na tabela de grupos associada à tabela de usuários, pois infere-se que esta coluna deverá ter o mesmo nome nas duas tabelas. No nosso caso, tanto na tabela USUARIO quanto na tabela GRUPO_USUARIO esta coluna chama-se LOGIN;
  • Digest algorithm: Nome do algoritmo de criptografia utilizado para armazenar as senhas. Existem algumas alternativas pré-definidas (none, SHA e MD5).
... na Aplica
ção Web

Após realizar a configuração necessária no servidor de aplicações é chegado o instante de configurar a aplicação para utilizar o JAAS. Esta configuração é feita em 3 níveis: arquivo WEB-INF/web.xml, anotações nos métodos e definição de exibição de componentes, mediante credenciais, nos arquivos JSP.

Começaremos configurando o arquivo WEB-INF/web.xml. Para isto, iremos abrir este arquivo no Netbeans (do projeto no qual queremos adicionar o JAAS) e utilizaremos o seu editor gráfico para facilitar esta tarefa:

Devemos selecionar a aba "Segurança":

Neste formulário iremos definir a princípio qual o realm ("nome do território" na tradução para português do Netbeans) e qual a configuração de login que iremos utilizar. Para isto devemos informar o nome do realm que criamos no servidor de aplicação e devemos selecionar qual configuração de login desejada, no nosso caso será form (ou formulário).

Para este exemplo, informamos que a página que será utilizada como formulário de autenticação será definida pela url "/login.jsf" e que caso a autenticação não corra bem, será exibida uma outra página definida pela url "/login_error.jsf".

Logo abaixo, vê-se as áreas para a definição das credenciais (papéis de segurança / security roles) e para a configuração das restrições de segurança para a aplicação:

Na área reservada para os papéis de segurança, deve-se informar quais são as credenciais (nome e descrição) que estarão envolvidas na aplicação. Em todos os outros pontos, quando forem referidos papéis, deve-se utilizar apenas os nomes que foram configurados nesta área.

Quanto à área reservada para as restrições de segurança, deve-se configurar que papéis podem acessar determinadas áreas da aplicação (definidas através de padrões de url).

Neste exemplo, criamos os papéis admin-role e user-role. Estes papéis definem, respectivamente, os usuários com privilégios administrativos e usuários em geral da aplicação.

Em seguida, iremos criar pelo menos duas restrições de segurança, uma chamada "Área Administrativa" que define que apenas usuários com credenciais "admin-role" podem acessar urls com o padrão "/admin/*"; e outra restrição de segurança, chamada "Zona Privativa" que define que apenas usuários com credenciais "admin-role" e "user-role" podem acessar urls com o padrão "/privado/*". Logo, infere-se, que quaisquer outros padrões de urls da aplicação podem ser acessados por usuários que não estejam autenticados. Em um caso extremo, onde nenhuma página pode ser acessada por usuários não autenticados, o desenvolvedor pode configurar uma restrição de segurança para o padrão de url "/*".

Vejamos a seguir o diálogo para definição de uma restrição de segurança:

Vejamos agora como foram definidos os formulários de login em caso de sucesso e em caso de erro.

Trecho do arquivo login.xhtml:

Atenção para a ação do formulário e os nomes dos campos de login e de senha. Estes nomes (j_security_check, j_username e j_password, respectivamente) são definidos pelo JAAS e são encaminhados automaticamente para o login module configurado (no nosso caso, para o JDBC Realm).

O arquivo login_error.xhtml não possui nada de especial, ele apenas exibe uma mensagem de erro para o usuário.

Se você chegou até este ponto do post, parabéns!, você já deve ter uma aplicação JEE com segurança JAAS funcionando. Podem executar e testar a aplicação.

... na Lógica de Negócio

Porém os mecanismos de segurança que a plataforma JEE nos provê são muito mais abrangentes. Veremos agora como definir restrições de acesso para os métodos da aplicação. Estas restrições devem ser adicionadas nos métodos das facades, para impedir/regular o acesso a estes métodos. Alguém pode pergutar-se acerca da necessidade de adicionar este nível de restrição de segurança, já que na camada de apresentação (a partir de padrões de URL) a aplicação já está segura. Existem duas respostas simples: programadores são falíveis e podem esquecer alguma restrição importante na camada de apresentação; invasores e/ou programadores maliciosos podem tentar acessar diretamente às fachadas da aplicação (principalmente se estas forem acessíveis remotamente via JNDI ou WebServices, por exemplo).

As restrições de segurança para os métodos são definidas utilizando-se a anotação @RolesAllowed (aplicadas a métodos e/ou classes). Quando esta anotação é aplicada a um método, ela define que papéis podem executar este método, quando ela é aplicada a uma classe ela define as restrições de acesso para todos os métodos desta classe. É importante destacar que estas anotações apenas surtirão efeito em classes instanciadas pelo servidor de aplicação (EJBs, WebServices, Backing Beans, etc).

Exemplo:

... na Camada de Visão

Finalmente, ainda é possível condicionar a exibição de componentes nas nossas páginas jsp (jspx, xhtml, etc) mediante as credenciais do usuário autenticado. No nosso caso, utilizamos a tecnologia JSF, implementação de referência da Sun (Mojarra) com a biblioteca de componentes Richfaces (ufa!).

Para realizar esta verificação devemos utilizar o atributo rendered, existente em todos os componentes de biblioteca padrão JSF bem como nos componentes definidos pelo Richfaces. Neste atributo deve-se informar uma expressão booleana (em EL - expression language) que irá determinar a exibição (caso a expressão retorne true) ou a ocultação (caso a expressão retorne false) do componente em questão.

Além disto, o Richfaces provê uma função no contexto EL, chamada rich:isUserInRole que deve ser usada nos atributos rendered para o condicionamento da exibição mediante às credenciais do usuário corrente.

Exemplo:

rendered="#{rich:isUserInRole('usuario-role, admin-role')}"

Conclusão:

Pessoal, espero ter conseguido passar uma introdução à tecnologia JAAS de uma forma rápida (sintam-se livres para postar perguntas).

Espero também que todos vejam esta tecnologia como uma ótima alternativa no instante de implementar autorização/autenticação em suas aplicações ao invés de estarmos sempre implementando nossos próprios esquemas de segurança ad-hoc.

Abraços.

7 comentários:

  1. Putz... Por fim posso dizer que ¨tenho uma idéia¨ de como funciona todos os possíveis passos de se implementar segurança com JAAS.

    Agradeço o resumão mega esclarecedor.

    ResponderExcluir
  2. Because security role mapping happens at deployment time, the default mapping must be turned on before the application is deployed. To turn on the default mapping, choose Configuration -> Security in the admin console. Click Enabled next to Default Principal to Role Mapping and Save.

    ResponderExcluir
  3. Como recupero o usuário logado pelo ManagedBean?

    ResponderExcluir
  4. Olá Marcos pra recuperar o usuário logado você faz isso dentro do ManagedBean:

    // Captura o Usuário da sessão
    private ExternalContext usuarioDaSessao() {
    FacesContext c = FacesContext.getCurrentInstance();
    ExternalContext user = c.getExternalContext();
    return user;
    }

    ResponderExcluir
  5. Este comentário foi removido pelo autor.

    ResponderExcluir
  6. e dentro do metodo que que você que chamar o usuario logado, faz assim::

    system.out.println(usuarioDaSessao().
    getUserPrincipal().getName());

    assim você consegue obter o usuário logado.. Valeu...

    ResponderExcluir
  7. Olá!
    Parabéns, excelente artigo.
    Estava para desenvolver a parte de login utilizando PhaseListener, mas vejo que assim é bem mais fácil, seguro...
    Gostaria de saber se não dá para disponibilizar o conteúdo da parte das camadas de visão e negócio...
    Valeu

    ResponderExcluir