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.