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.
Falaremos mais um pouco sobre as diferentes opções para a configuração do login (login config):
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¨:
... 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.
terça-feira, 28 de julho de 2009
Java Authentication and Authorization Service (JAAS)
Assinar:
Postar comentários (Atom)
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.
ResponderExcluirAgradeço o resumão mega esclarecedor.
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.
ResponderExcluirComo recupero o usuário logado pelo ManagedBean?
ResponderExcluirOlá Marcos pra recuperar o usuário logado você faz isso dentro do ManagedBean:
ResponderExcluir// Captura o Usuário da sessão
private ExternalContext usuarioDaSessao() {
FacesContext c = FacesContext.getCurrentInstance();
ExternalContext user = c.getExternalContext();
return user;
}
Este comentário foi removido pelo autor.
ResponderExcluire dentro do metodo que que você que chamar o usuario logado, faz assim::
ResponderExcluirsystem.out.println(usuarioDaSessao().
getUserPrincipal().getName());
assim você consegue obter o usuário logado.. Valeu...
Olá!
ResponderExcluirParabé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