JAAS e Seam – O útil ao agradável

fevereiro 10, 2009

Quando se fala em autorização e autenticação Java, nada mais natural do que pensar em JAASJava Authentication and Authorization Service.

Na realidade o poder do JAAS provém de sua arquitetura plugável, o que permite aos Servidores de Aplicação JavaEE utilizarem sua estrutura tanto para autenticação/autorização em componentes EJB, como em aplicações web ou serviços de mensageria. Além disso a implementação do acesso aos dados de autenticação também fica abstraída da sua aplicaçao, ou seja, não é necessario saber se esta autenticando em LDAP, database ou file-systems, quem resolve isso é a configuração login-module do JAAS de seu Servidor.

Mas mesmo assim o Security Service do servidor de aplicação é limitado. Como não poderia deixar de ser, o JAAS implementado pelo container só pode agir sobre objetos gerenciados pelo container como EJBs e Servlets, ou seja, os POJOs de sua aplicação fica de fora. Outra limitação é que embora possa restringir contextos de páginas pelo serviço de segurança JAAS do container, ele não é capaz de restringir acesso ou visibilidade de “trechos” de uma página, como uma caixa de texto por exemplo.

Por essas e outras, soluções “paralelas” de segurança começaram explodir pela marginalidade da especificação, tendo destaque o Spring Security (ACEGI) e o Seam Security, cobrindo as brechas deixadas pelos servidores.

A solução do Seam para segurança é bastante completa e pode ser uma simples configuração de papéis e credenciais, uma elaborada solução de Identity Managment out-of-box para databases ou LDAP, marcadores baseados em anotações com políticas de acesso para POJOs, EJBs, páginas e trechos de páginas ou ainda complexas regras codificadas em um engine de regras (Drools) externas a aplicação. Na verdade, apenas com a configuração de credenciais e papéis já dá pra fazer MUITA coisa.

Por exemplo:

public class Authenticator implements AuthenticatorI{

    @In Identity identity;
    @In Credentials credentials;
    @In UserRepository userRepository;
    @Out(required=false) User user;

    public boolean authenticate(){

        //aqui lógica de autenticacao XPTO
        User user  = userRepository.getByCredentials(
                        credentials.getUsername(),
                        credentials.getPassword());

        if(user!=null){
                for(Role role:user.getRoles()){
                    identity.addRole(role);
                }
                return true;
        }
        return false
    }
}

Na autenticação bem tradicional do Seam logo acima, vários papéis(roles) são adicionados a identity no login. Com isso é possível fazer:

– proteger uma classe:

@Restrict(“#{s:hasRole(‘auditor’)}”)
public class Inventory(){
….
}

– um método:

@Restrict(“#{s:hasRole(‘mailer’)}”)

public void sender(Mail mail){
….
}

– trechos de páginas

<h:commandButton action=#{car.run} value=”Run” rendered=”#{s:hasRole(‘booster’)}”>

O problema é: as vezes uma aplicação baseada em Seam Security precisa acessar EJBs protegidos no Security Service do servidor de aplicação. Pra piorar, o Seam apesar de lidar bem com seus componentes, ele nativamente não propaga sua autenticação/autoriazação para os objetos gerenciados apenas pelo container.

Para superar isso no JBoss é simples. No diretório do JbossWeb (tomcat embarcado) no servidor, existe um jar chamado jbossweb-service.jar. Este jar possui a classe responsável pela autenticação JAAS do container web, e propaga isso também para o container EJB. Então, apenas é necessário adicionar este jar ao classpath e alterar a classe de autenticação para algo como:

    public boolean authenticate(){

        //aqui lógica de autenticacao XPTO
        User user  = userRepository.getByCredentials(
                                credentials.getUsername(),
                                credentials.getPassword());

        if(user!=null){
                for(Role role:user.getRoles()){
                    identity.addRole(role);
                }

                //propagando para o container
                WebAuthentication webAuthentication = new WebAuthentication();
                webAuthentication.login(credentials.getUsername(),
                                        credentials.getPassword());

                return true;
        }
     }

Além de fornecer as credenciais para o container, também é necessário dizer qual a Application-Policy JAAS do container será usada para fornecer as roles desta autenticação. Para tal, basta adicionar o arquivo jboss-web.xml no war do projeto (em WEB-INF), contendo:

<jboss-web>
    <security-domain flushOnSessionInvalidation="true">
       java:/jaas/nomeDaSuaRelmJAAS
    </security-domain>
</jboss-web>

Beleza, agora você pode acessar tanto EJBs e POJOs protegidos pelo Seam, como EJBs isolados protegidos pelo container.

Hmmm, mas perae, ainda parece não estar legal. Parece que estou duplicando código. O mesmo código que uso para pesquisar o usuário, recuperar suas Roles e adicionar para o Seam, o container vai ter que fazer também. Então porque não uso diretamente o código de autenticacao do container também para o Seam? Isso me parece razoável e possível.

Para isso temos que modificar a tag security do components.xml:

disso:

<security:identity authenticate-method="#{authenticator.authenticate}" />

para isso:

<security:identity  jaas-config-name="nomeDaSuaRelmJAAS"/>

Quando é feita essa configuração não precisamos mais implementar uma classe de Autenticação que fara uma pesquisa em algum lugar retornando o usuário e seus direitos, o container faz isso pra gente. Isso é possível porque o Seam obedece a mesma estrutura de Autenticação/Autorização do container, ou seja, também é baseada em JAAS, logo pode delegar para o Server esta tarefa.

Mas o problema de propagação para o container ainda existe, aquele código de WebAuthentication ainda é necessário. Mas agora fica a pergunta, se não temos mais a classe de autenticação já que o container esta fazendo isso, onde colocar este código extra?

O Seam possui suporte a configuração de Listeners e Observers para QUALQUER um de seus componentes. É bem simples fazer uma configuração que represente a idéia: “Sempre que ocorrer um evento de Login, execute determinado código”. Na prática isso seria:

login.page.xml

   <navigation from-action="#{identity.login}">
     <rule if="#{identity.loggedIn}">
         <raise-event type="loginEvent"/>
         <redirect view-id="/home.xhtml"/>
      </rule>
   </navigation>

Com isso configuramos um evento chamado loginEvent. Agora o Observer do evento:

@Name("loginListener")
public class LoginListener {
        @In
        private Identity identity;

        @Observer("loginEvent")
        public void configJaas(){
                WebAuthentication webAuthentication = new WebAuthentication();
                webAuthentication.login(identity.getCredentials().getUsername(),
                                        identity.getCredentials().getPassword());
        }
}

Pronto!!! Agora através da mesma configuração no servidor podemos ter configurados EJBs Standalones, Webservices, Filas de mensagem e a própria aplicação Seam, tudo no servidor. Se a configuração da aplicação para buscar usuarios e direitos mudar de banco de dados para LDAP, FileSystem ou qualquer outra coisa, nenhuma linha da aplicação precisará ser alterada.

Ops, tem uma pegadinha neste “identity.getCredentials().getPassword()” do último código que deixo para o próximo post 😉