Muitas são as aplicações web em Java que utilizam o Serviço de Autenticação e Autorização do Java (JAAS) para fazer a segurança. E um elemento famoso no JAAS é o JSP/HTML de login que possui um formulário mágico que faz um POST para a URL “j_security_check“, enviando o password e a senha através dos inputs de nome “j_password” e “j_username” respectivamente. Muitos torcem o nariz para esse tipo de coisa, já que a plataforma está de certa forma engessando a tela de login.
As vantagens de querer fazer um formulário de login JSF 2 são muitas! Primeiramente você pode querer herdar o layout de um template via Facelets. Você pode também querer utilizar componentes ricos de bibliotecas como PrimeFaces ao invés de simples inputs HTML padrão. O JSF também dá um bom suporte para validação de campos e mensagens de erro/sucesso. Mas dadas as restrições impostas pelo j_security_check e seus amigos, como unir os dois mundos então?
Com a chegada do JEE 6 e a nova Servlet API 3.0, foi previsto um método bem óbvio na interface HttpServletRequest chamado request.login(String username, String password). O resto você provavelmente já imagina: pode-se acionar o JAAS programaticamente dentro de um ManagedBean do JSF por exemplo, unindo o melhor dos dois mundos.
A idéia então é fazer um XHTML de login e fazer um ManagedBean que cuide do comportamento dessa tela. Veja como fica a configuração de segurança do web.xml:
<?xml version="1.0"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
...
<security-constraint>
<web-resource-collection>
<web-resource-name>private</web-resource-name>
<description>private resources</description>
<url-pattern>/private/*</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>/public/pages/security/login.jsf</form-login-page>
<form-error-page>/public/pages/security/login.jsf</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>*</role-name>
</security-role>
</web-app>
|
Perceba que no web.xml a página de login é tanto o formulário quanto a página de erro, ou seja, em caso de login inválido a aplicação retornará para a página de login, permitindo continuar interagindo com o formulário para exibir mensagens por exemplo. A página /public/pages/security/login.xhtml pode ser algo assim (observe que ela herda um template via Facelets):
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/WEB-INF/template/main.xhtml">
<ui:define name="body">
<p:messages id="messages" globalOnly="true" />
<h:form id="form">
<h:panelGrid columns="3">
<p:outputLabel value="Usuário" for="inputUsuario" />
<p:inputText id="inputUsuario" required="true"
value="#{loginMB.usuario}" />
<p:message for="inputUsuario" />
<p:outputLabel value="Senha" for="inputSenha" />
<p:password id="inputSenha" required="true"
value="#{loginMB.senha}" />
<p:message for="inputSenha" />
</h:panelGrid>
<p:commandButton id="btEntrar" value="Entrar" ajax="false"
action="#{loginMB.onClickLogar()}"
icon="ui-icon-check" />
</h:form>
</ui:define>
</ui:composition>
|
E um exemplo de ManagedBean para tal página pode ser (os imports estão omitidos):
...
@ManagedBean
@ViewScoped
public class LoginMB {
private static final String PAGINA_INDEX = "/private/pages/index.xhtml";
private String usuario;
private String senha;
public String onClickLogar() {
try {
HttpServletRequest request = (HttpServletRequest) FacesContext.
getCurrentInstance().getExternalContext().getRequest();
request.login(this.usuario, this.senha);
return PAGINA_INDEX;
} catch (ServletException e) {
//se houver erro no Login Module vai cair aqui...
//Você pode fazer log por exemplo!
} finally {
//tratar aqui mensagens de segurança que possam ter vindo
//do Login Module exibindo-as na forma de FacesMessage
}
return null;
}
public String getUsuario() {
return usuario;
}
public void setUsuario(String usuario) {
this.usuario = usuario;
}
public String getSenha() {
return senha;
}
public void setSenha(String senha) {
this.senha = senha;
}
}
|
Não se esqueça de que para esse recurso de login programático estar disponível, sua aplicação deve adicionar a dependência provida a API do Servlet 3.0 no pom.xml:
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
|
Vale ressaltar que o exemplo do post cobre apenas o essencial. Para atingir a excelência num esquema de login JAAS é preciso cuidar de vários outros aspectos. Um deles é definir um bom mecanismo de troca de mensagens do Login Module para com a Aplicação. Uma opção é adicionar a mensagem como atributo no Request, podendo capturá-la no ManagedBean e tranformá-la em uma FacesMessage. Outra questão que deve ser observada é o comportamento da aplicação quando a página de login é acessada por um usuário já logado, podendo redirecioná-lo ou retirá-lo da sessão, o que pode ser feito via JSF adicionando no seu XHTML de login um event listener na fase de Pre-Render View e implementando um listener correspondente:
...
<f:event listener="#{loginMB.verificaSeUsuarioJaLogado}" type="preRenderView" />
...
|
Tenho certeza que partindo dessa solução base você poderá buscar várias sofisticações, fugindo do tradicional JSP de login para o JAAS.