JAAS e Seam – O útil ao agradável

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 😉

Anúncios

14 Responses to JAAS e Seam – O útil ao agradável

  1. Diogo Santos disse:

    Mesmo na sua segunda solução a autenticação é feita duas vezes.

    Eu implemente de forma muito parecida.

    No autenticator do Seam para validar a senha do usuário eu fiz pelo próprio WebAutheticator.

    if(webAuthentication.login(credentials.getUsername(),
    credentials.getPassword())) {
    ……
    //código para recuperar roles
    ……
    return true;

    } else {

    return false;
    }

    Mas mesmo assim eu preciso fazer outra consulta no banco para recuperar as roles do usuário.

    Uma outra vantagem de fazer da forma que sugeri é que é possível inicializar o objeto Actor que é usado no JBMP(Business Context do Seam).

    • alessandrolazarotti disse:

      Seu código se enquandra bem no que tentei demonstrar na primeira solução (realmente não é necessário escrever duas vezes a checagem de usuário e senha). Contudo sua aplicação continua tendo que saber onde buscar o resto das informações da autenticação, no caso as roles. O que quis demonstrar na segunda solução é que é possível delegar todo o serviço de autenticação para o container.

      A necessidade de chamada duplicada a autenticação esta para ser corrigida no release 2.1.2 GA, isso é, se a feature não for prorrogada =)

  2. alessandrolazarotti disse:

    … vc pode inicializar o Actor no Observer.

  3. Rafael Ponte disse:

    Muito bom post Lazarotti.

    Utilizar uma engine de segurança especializada em aplicações sérias é o ideal. Seja JAAS, Spring Security, Seam Security etc.

    Enfim, excelente post, parabéns!
    Estamos de olho, rs.

  4. Ricardo Martinelli de Oliveira disse:

    Olá Alessandro,

    Lendo o capítulo de Security no Seam, me deparei com a seção 15.6.6 – Typesafe Permission Annotations e não entendi muito bem o que se pode fazer com essa anotações. Alguma vez você as utilizou? Qual a finalidade dessas anotações?

  5. alessandrolazarotti disse:

    Quando você utiliza a anotação @Restrict em um método, o Seam vai tentar bater o nome da classe e o nome deste método em uma das regras de permissão catalogadas no arquivo .drl do Drools de sua aplicação.
    Por exemplo, uma classe de nome Auditoria e seu método finalizarInspecao anotado com @Restrict, teria no Drools uma entrada como ” c: PermissionCheck(target == “auditoria”, action == “finalizarInspecao”).
    Você pode criar uma anotacao typesafe @Finalizar(Inspecao.class) que pode ser mais representativa para a regra, e para o código. Assim, em qualquer método que você inserir essa validação irá procurar por c: PermissionCheck(target == “inspecao”, action == “finalizar”).
    O mesmo você pode fazer com Roles: em vez de inserir a anotação @Restrict(“#{s:hasRole(‘approval’)}”), você pode criar sua anotação typesafe @Approval, extendendo a do Seam, conforme documentação. A vantagem? Legibilidade do código, apenas isso.

  6. Vinicius Senger disse:

    Laza,

    Vale lembrar o seguinte: apesar de que podemos em alguns casos justificar o acesso a um EJB legado que já esta em produção, temos a limitação dele ser um EJB local para usar injeção e também dependendo do sistema de classloading configurado pode-se ter problemas.

    No entanto, vale também a pena pensar no seguinte:

    – EJB é um modelo de componentes;
    – Sendo um EJB um componente porque não embarcar o JAR do EJB no EAR do Seam? Desta forma estamos criando apenas uma nova instancia de um mesmo componente provendo isolamento de versão além de permitir que um aplicativo X utilize um EJB com por exemplo 2 threads e um aplicativo Y utilize o mesmo componente EJB com 5 threads.

    Se sairmos plugando vários EAR acessando uma mesma instancia de componente EJB no application server temos a pseudo-facilidade de manter apenas uma instancia e perdemos toda a capacidade de configuração individual do componente conforme necessidade de aplicativo.

    O que você acha?

  7. alessandrolazarotti disse:

    Faala @Senger!

    Acredito que isso depende muito das aplicações envolvidas. Geralmente quando um EAR Seam e um outro EAR convencional (ou um javaSE standalone) necessitam acessar um mesmo componente EJB, pode ser muito complicado gerenciar versões diferentes deste mesmo componente. Normalmente a aplicação Seam irá querer se valer das suas anotações particulares para lidar com segurança (e isso não pode ser feito via deployment descriptor), enquanto a outra versão do mesmo componente irá se basear no JAAS tradicional (via xml ou anotação). Portanto, as duas cópias do componente não seriam idênticas o que tornariam difĩceis de manter. Claro que você poderia levar as libs do Seam para satisfazer as anotações em um outro EAR não Seam, apenas para não fazer outra versão, mas esse “xunxo” não parece uma boa alternativa. Se for dois EAR utilizando um componente EJB semelhante em comum tudo bem, é gerenciável, mas imagine em uma arquitetura moderna onde EJBs Remotos não são simplesmente uns caras com “lógicas de negócio”, mas são “Serviços”, que podem ser plugados em um barramento ESB e servirem a “X” clientes distintos, onde alguns podem inclusive acessá-los diretamente … as possibilidades são muitas.

    Em meu ver o ponto de corte é exatamente este: é um Serviço, então é um @Remote compartilhado (assim como poderia ser um WebService SOA/REST, Burlap, Resian, MDB, etc). É uma simples regra pertinente ao domínio, então é um @Local e pode ser embutido em vários EARs e cada um assume sua responsabilidade junto ao componente (já que ele pode ser específico do domínio).

    Já a questão de ter que ser um EJB Local para injeção no Seam, isso pode ser resolvido. Você pode fazer um componente Seam chamado por exemplo de “@Name(‘customerService’)” e este ter um método como “@Unwrap CustomerService lookupEjb(){ … }” e neste método vc faz o lookup da forma que quiser. O barato é que quando outro componente Seam fazer algum @In(“customerService”), o método anotado como @Unwrap é invocado e o ejb remoto é recuperado de forma transparente, como se fosse um componente Seam, ou seja, seus @In acabam injetando o EJB Remoto.

    []s

  8. alessandrolazarotti disse:

    … fora Vinicius, as outras “tentações” que os desenvolvedores Seam teriam que abrir mão de usar para não terem problemas de compatibilidade nas versões de seus EJBs, como o uso de @DataModel, @In(‘facesMessage’), outcomes de navegação, etc

  9. Vinicius Senger disse:

    Pois é, neste caso é possível fazer um Seam Component warpper do outro EJB, mas de qualquer forma compartilho da sua opinião: local é componente, remote é um bom passo dado para serviço. Acho que nos dias de SOA não basta ser remote, mas esse é o primeiro passo.

    Serviço deveria ser auditável, rastreável, tarifável, ou seja, impõe governanças completamente diferentes do que governança de componentes: compatibilidade, dependencias, lib externas, etc.

    Aproveito para aproveitar e questionar sobre a seguinte possibilidade: Seam + SAR + Jboss 5 + OSGi, já é realidade?

    []s
    Vinicius

  10. Vinicius Senger disse:

    Ah, mais um detalhe importante é que sendo EJB um componente com parte de glueing declarado em XML, não vejo problema em ter um mesmo JAR com o .class e outro EJB-JAR com o JAR + XML de glueing code..

  11. alessandrolazarotti disse:

    Sim é possível fazer um wrapper ou qualquer outra forma de isolar o componente, mas tem que mensurar quanto vai se ganhar com isso (a reusabilidade ‘cross arquiteturas heterogênias’realmente é planejada?). Como disse no outro comentário, tudo depende dos requisitos da aplicação e da razão do componente (ira compor um serviço;são simples regras;apenas quer se valer dos recursos de transação e gerencia do container;etc).

    A idéia do Seam é simplificar o desenvolvimento, esta é a sua motivação. Temos que tomar cuidado para não cairmos em uma subutilização do framework e sobreengenharia sem necessidade (algo como que acontecia nos tempos de Core J2EE Patterns).

    Quanto a questão de Serviços concordo plenamente. Citei apenas como uma métrica sobre decidir quanto a sua componentização. Muitas outras coisas se somariam a isso, sem dúvida.

  12. alessandrolazarotti disse:

    O JBoss 5, seu core, o JBoss Microntainer possui uma interface que oferece API OSGI através de um “module” (int-osgi) para interagir com os serviços do microcontainer, além de disponibilizar um OSGI Classloader. Em breve estará disponível a documentação referente a interação e integração OSGI 😉

    Seam + SAR não conheço nenhum trabalho neste sentido. Tente abrir um feature request no Jira.

    Abraços

  13. Manuella disse:

    Alessandro vi teu blog e sua participação na Comunidade JBoss Brasil, você teria algum tutorial (ou indicaria algum) sobre o SeamTest?

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: