Demonstraremos como utilizar JAAS+EJB 3.1 com Demoiselle 2.0 no JBoss 6.0.
O Framework Demoiselle em sua versão 2.x trás diversas mudanças e melhorias com relação à versão anterior, sendo a principal delas a sua completa aderência à especificação JSR 316: Java Platform, Enterprise Edition 6, ou simplesmente Java EE 6. Assim, a compatibilidade com soluções voltadas à esta especificação é garantida.
Por isso a utilização do JAAS(Java Authentication and Authorization Service) como solução de autenticação e EJB 3.1 para Autorização é relativametne simples de ser efetuada.
Relembrando para aqueles que já conhecem, ou esclarecendo para os demais, na versão anterior do Demoiselle havia uma solução de segurança baseada também no JAAS que provia uma anotação (@RequiredRole) que era utilizada para anotar os métodos no Bussiness Controller e assim garantir o controle de acesso.
Na versão atual é possível que se utilize as facilidades providas pela especificação EJB 3.1 que já é implementada pelos servidores de aplicação como é o caso do JBoss 6.0 que será demonstrado neste texto.
A razão de um exemplo especifico para um determinado servidor de aplicação é porque cada implementação servidor possuí alguns pequenos detalhes em sua solução, mas a maioria das instruções são semelhantes pela especificação EJB 3.1. Mas tentaremos abranger sempre que possível, em outros posts, o uso com outros servidores.
Para fins de entendimento e facilitar a explicação tomaremos como base a aplicação de exemplo chamada Contactlist-JSF (https://demoiselle.svn.sourceforge.net/svnroot/demoiselle/sample/tags/contactlist-2.0.0) que é uma implementação utilizando o Demoiselle 2 e o ambiente de Desenvolvimento o Eclipse Helios e Jboss 6.0 disponibilizado pelo projeto Infra (http://demoiselle.sourceforge.net/infra).
Feita a importação do projeto (File → Import → Check Out Maven Project from SCM), teremos uma aplicação funcional mas ainda sem nenhum mecanismo de autenticação e autorização. Consideraremos também que a integração com o servidor esteja configurada.
Primeiro, temos modificar o comportamento da aplicação para que ela acione os mecanismos do JAAS, alterando o arquivo web.xml (localizado em /src/main/webapp/WEB-INF/), incluindo as tag abaixo:
<security-constraint> <display-name>JAAS Security </display-name> <web-resource-collection> <web-resource-name>all</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/login.xhtml</form-login-page> <form-error-page>/access.xhtml</form-error-page> </form-login-config> </login-config>
Na tag login-config, apontamos as páginas (formulários) de login e error, que serão as paginas personalizadas para o projeto que irão responder nestes eventos. Isso porque informamos como FORM no <auth-method>. Se optar por não utilizar formulários, pode ser informado o método BASIC, mas neste caso não haverá controle dos casos de erros e nem personalização da página de login. Estas páginas deverão ficar no diretório /src/main/webapp/ ou em um subdiretório a partir deste (que deverá ser especificado na tag), as paginas estão listadas abaixo:
login.xhtml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Tela de Login</title>
</h:head>
<h:body>
<h2>Digite as informações de Login: </h2>
<form method="post" action="j_security_check">
<table style="width: 80%">
<tr>
<td align="right"><label for="username">Usuário</label></td>
<td align="left"><input type="text" id="username" name="j_username"/></td>
</tr>
<tr>
<td align="right"><label for="password">Senha</label></td>
<td align="left"><input type="password" id="password" name="j_password"/></td>
</tr>
<tr>
<td></td>
<td > <input type="submit" value="Entrar"/> </td>
</tr>
</table>
</form>
</h:body>
</html>
access.xhtml:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Erro de Login</title>
</h:head>
<h:body>
<h2>Erro de Permissão</h2>
<form method="post" action="j_security_check">
<h:messages />
<table style="width: 80%">
<tr>
<td align="right"><label for="username">Usuário</label></td>
<td align="left"><input type="text" id="username" name="j_username"/></td>
</tr>
<tr>
<td align="right"><label for="password">Senha</label></td>
<td align="left"><input type="password" id="password" name="j_password"/></td>
</tr>
<tr>
<td></td>
<td > <input type="submit" value="Tentar Novamente"/> </td>
</tr>
</table>
</form>
</h:body>
</html>
Com estas alterações, a aplicação já está preparada para fazer a chamada ao JAAS para efetuar a autenticação (login) na aplicação, ainda sem considerar as questões de autorização. Mas se executarmos a aplicação desta forma, apesar de não haver regras de acesso, não temos ainda as informações dos usuários e papéis.
O próximo passo é configurar o servidor Jboss 6.0 com as informações de usuários e papéis.
Identifique o diretório de instalação do servidor Jboss 6.0 (para usuários do pacote infra é /opt/demoiselle/server/jboss-6.0) e dentro dele encontre a pasta de configurações /server/default/conf. Nesta pasta inclua dois arquivos:
- defaultRoles.properties, é onde se define quais roles estarão definidas para cada usuário, exempo:
aluno=role_aluno professor=role_professor secretaria=role_secretaria, role_professor
- defaultUsers.properties, é a definição dos usuários e senhas, no exemplo abaixo criamos três usuários cujas senhas para cada um é o mesmo nome do usuário:
aluno=aluno professor=professor secretaria=secretaria
Até aqui, é o suficiente apenas para autenticação pelo servidor, mas é importante já testar a aplicação, fazendo o “deploy” e iniciando o servidor. De acordo com o exemplo acima informe o usuário e senha para acessar a aplicação. Esse teste é necessário para garantir que o que foi feito até o momento está correto e as demais alterações dependem desta parte. Veja o exemplo da aplicação executando:
Agora que já temos uma tela de Autenticação, podemos partir para as modificações que irão garantir o controle de permissões por papel, e para isso utilizaremos as funcionalidades do EJB 3.1.
Para utilização do EJB 3.1, é necessária a inclusão das dependências no arquivo de configuração do Maven, o POM.XML, incluindo as seguintes linhas dentro da tag <dependecies>:
<dependency>
<groupId>org.jboss.ejb3</groupId>
<artifactId>jboss-ejb3-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.ejb3</groupId>
<artifactId>jboss-ejb3-ext-api</artifactId>
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
Note que as dependências estão com o escopo “provided” que nos informa que as bibliotecas já estão disponíveis no Jboss 6.0, mas precisamos apontá-las no projeto para conseguir fazer a compilação dentro do Eclipse.
Por definição, para usarmos as funcionalidades do EJB 3.1, precisamos obviamente de EJBs na nossa aplicação. E começaremos criando um novo pacote que conterá estas implementações, conforme exemplificado na figura abaixo:
Uma das peculiaridades do Jboss é que devemos informar ao EJB qual é o domínio do login de segurança utilizaremos, que é uma referência a um
<application-policy>
configurado no arquivo login-config.xml (…/server/default/conf/).
No nosso exemplo não faremos nenhum personalizado e utilizaremos o “other” que vem por padrão. Faremos isso criando uma interface chamada Domain.java para armazenar essa informação e assim não ficar replicando constantes, ficando assim:
package contactlist.ejb;
public interface Domain {
String OTHER = "other";
}
Seguindo o mesmo padrão, vamos definir outra interface chamada Roles.java para os papéis (role names) que foram configurados no Jboss, da seguinte forma:
package contactlist.ejb;
public interface Roles {
/**
* Apenas consultas
*/
String ALUNO = "role_aluno";
/**
* Não executará exclusões.
*/
String PROFESSOR = "role_professor";
/**
* Acesso à todas as funções
*/
String SECRETARIA = "role_secretaria";
}
A estratégia para criação dos EJBs neste exemplo, será de criar um para cada BusinessController da aplicação. Da forma como ainda se encontra, o ManagedBean (@ViewController) aciona os métodos do BusinessController, mas para utilizarmos a estratégia do EJB, criaremos os EJBs como fachadas (@FacadeController) que farão esta interface entre os Managed Beans e os BusinessController. Se desejasse poderia criar apenas um EJB. Usaremos a anotação @Stateless para criar os EJBs, que é o mais óbvio devido à simplicidade da aplicação. Desta forma o nosso código será o seguinte:
ContactEJB.java:
package contactlist.ejb;
import java.util.List;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.inject.Inject;
import org.jboss.ejb3.annotation.SecurityDomain;
import contactlist.business.ContactBC;
import contactlist.domain.Contact;
import br.gov.frameworkdemoiselle.stereotype.FacadeController;;
@Stateless
@SecurityDomain(Domain.OTHER)
@FacadeController
public class ContactEJB {
@Inject
private ContactBC contactBC;
@RolesAllowed({Roles.SECRETARIA})
public void delete(Long id) {
contactBC.delete(id);
}
@RolesAllowed({Roles.PROFESSOR, Roles.SECRETARIA})
public void insert(final Contact contact) {
contactBC.insert(contact);
}
@RolesAllowed({Roles.PROFESSOR, Roles.SECRETARIA})
public void update(Contact contact) {
contactBC.update(contact);
}
@PermitAll
public Contact load(Long id) {
return contactBC.load(id);
}
@PermitAll
public List<Contact> findAll() {
return contactBC.findAll();
}
}
AuditEJB.java:
package contactlist.ejb;
import java.util.List;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.inject.Inject;
import org.jboss.ejb3.annotation.SecurityDomain;
import br.gov.frameworkdemoiselle.stereotype.FacadeController;
import contactlist.business.AuditBC;
import contactlist.domain.Audit;
@Stateless
@SecurityDomain(Domain.OTHER)
@FacadeController
public class AuditEJB {
@Inject
private AuditBC auditBC;
// Apenas os usuários da secretaria
@RolesAllowed({Roles.SECRETARIA})
public List<Audit> findAll() {
return auditBC.findAll();
}
}
Agora temos que alterar os Managed Beans, que estão no pacote contactlist.view, para utilizar os EJBs. Que ficarão respectivamente assim:
AuditMB.java:
package contactlist.view;
import java.util.List;
import javax.inject.Inject;
import br.gov.frameworkdemoiselle.stereotype.ViewController;
import contactlist.domain.Audit;
import contactlist.ejb.AuditEJB;
import contactlist.template.PagedListPageBean;
@ViewController
public class AuditMB extends PagedListPageBean<Audit, Long> {
private static final long serialVersionUID = 1L;
@Inject
private AuditEJB auditEJB;
@Override
protected List<Audit> handleResultList() {
return auditEJB.findAll();
}
@Override
protected void handleDelete(Long id) {
// Does nothing
}
}
ContactListMB.java
package contactlist.view;
import static contactlist.view.ViewId.CONTACT_EDIT;
import java.util.List;
import javax.inject.Inject;
import br.gov.frameworkdemoiselle.annotation.NextView;
import br.gov.frameworkdemoiselle.stereotype.ViewController;
import contactlist.domain.Contact;
import contactlist.ejb.ContactEJB;
import contactlist.template.PagedListPageBean;
@ViewController
@NextView(CONTACT_EDIT)
public class ContactListMB extends PagedListPageBean<Contact, Long> {
private static final long serialVersionUID = 1L;
@Inject
private ContactEJB contactEJB;
protected List<Contact> handleResultList() {
List<Contact> result = null;
result = contactEJB.findAll();
return result;
}
@Override
protected void handleDelete(Long id) {
contactEJB.delete(id);
}
}
ContactEditMB.java:
package contactlist.view;
import static contactlist.view.ViewId.CONTACT_LIST;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.SelectItem;
import javax.inject.Inject;
import br.gov.frameworkdemoiselle.annotation.Name;
import br.gov.frameworkdemoiselle.annotation.PreviousView;
import br.gov.frameworkdemoiselle.exception.ExceptionHandler;
import br.gov.frameworkdemoiselle.stereotype.ViewController;
import br.gov.frameworkdemoiselle.template.AbstractEditPageBean;
import br.gov.frameworkdemoiselle.transaction.Transactional;
import br.gov.frameworkdemoiselle.util.Faces;
import contactlist.domain.Contact;
import contactlist.domain.Phone;
import contactlist.domain.PhoneType;
import contactlist.ejb.ContactEJB;
import contactlist.exception.DuplicatedCpfException;
@ViewController
@PreviousView(CONTACT_LIST)
public class ContactEditMB extends AbstractEditPageBean<Contact, Long> {
private static final long serialVersionUID = 1L;
private DataModel<Phone> phones;
@Inject
private ContactEJB contactEJB;
@Inject
@Name("messages")
private ResourceBundle bundle;
public void addPhone() {
getBean().addPhone(new Phone());
}
public void deletePhone() {
getBean().getPhones().remove(getPhones().getRowData());
}
public DataModel<Phone> getPhones() {
if (phones == null) {
phones = new ListDataModel<Phone>(getBean().getPhones());
}
return phones;
}
@ExceptionHandler
public void handleException(final DuplicatedCpfException cause) {
Faces.addMessage("cpf", cause);
}
public List<SelectItem> getPhoneTypes() {
List<SelectItem> items = new ArrayList<SelectItem>();
for (PhoneType phoneType : PhoneType.values()) {
items.add(new SelectItem(phoneType, bundle.getString(phoneType.toString())));
}
return items;
}
@Override
@Transactional
public String delete() {
this.contactEJB.delete(getId());
return getPreviousView();
}
@Override
@Transactional
public String insert() {
this.contactEJB.insert(getBean());
return getPreviousView();
}
@Override
@Transactional
public String update() {
this.contactEJB.update(getBean());
return getPreviousView();
}
@Override
protected void handleLoad() {
setBean(this.contactEJB.load(getId()));
}
}
Este último código é o que mais sofreu alterações, devido ao fato de não usarmos o Template de Edição da aplicação que atualmente não está preparada para trabalhar com o EJB.
Agora basta republicar a aplicação e iniciar novamente.
Experimente logar com o usuário aluno por exemplo e tentar fazer alguma inclusão, alteração ou acessar a log de auditoria. O sistema apresentará o erro:
javax.servlet.ServletException: javax.ejb.EJBAccessException: Caller unauthorized
Como o objetivo do post é demonstrar o uso do EJB 3.1 para controle de autorização, não exemplificaremos como tratar estas exceções. Para melhorar o código ou preparar sua própria aplicação consulte a documentação neste link: http://demoiselle.sourceforge.net/docs/





