Abandonando o antigo formulário j_security_check através de JSF 2 e Servlet 3

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.

10 comentários em “Abandonando o antigo formulário j_security_check através de JSF 2 e Servlet 3”

    1. Quando você usa JAAS você nao precisa de filtro algum. O próprio container faz a segurança baseado nas regras descritas no web.xml, conectando sua aplicação a um serviço de Autenticação e Autorização que pode ser reutilizado por várias aplicações inclusive. Assim como delegamos para o container gerenciar pool de conexões de banco de dados, datasources, etc… podemos e devemos dentro possível fazer o mesmo com segurança. As vantagens são muitas. Mas sim, é possível fazer a segurança “na mão” seja por meio de filtros e listeners da API Servlet, seja por meio de phase listeners do JSF. Se for uma escolha consciente, não vejo problemas. Mas as chances são de você implementar um mecanismo de segurança muito inferior aos já consolidados por aí. Procure soluções alternativas e mais fáceis, como o http://shiro.apache.org/.

  1. Olá, tudo bem?
    Gostaria de compreender melhor esta sua proposição. Existem a possibilidade de ceder o código fonte?

    vlw

    1. Olá Alberto! Os exemplos que tenho são de sistemas em produção que infelizmente não posso ceder. Mas o código mostrado no Post é bastante esclarecedor, desde que você tenha conhecimento de JAAS e JSF, os dois tópicos abordados. Até+

  2. Oi Rafael,
    Hoje a forma que eu faço a autorização e autenticação nos meus projetos é bem manual, heranças que vieram de outras linguagens que eu programava antes de java e que não tinham a figura do servidor de aplicação. Eu gostaria de experimentar o JAAS e deixar essa tarefa por conta do servidor java. Você tem como indicar livros ou tutoriais ou qualquer outra forma de estudo de JAAS de preferência direcionado ao JAVA EE 6 e JBOSS ?
    Obrigado pelo artigo, foi bem esclarecedor pra mim.

    1. Marco,

      Não conheço livros, aprendi grande parte na prática. Como o JAAS é provido pelo servidor de aplicação, geralmente os materiais dos tutoriais e documentações de cada servidor são mais completos. Na internet há posts e tutoriais.

      Uma dica valiosa é que criar um LoginModule JAAS do zero é besteira. Geralmente os servidores já dão LoginModules dinâmicos, classes já providas pelo servidor, e a única coisa que você precisa é parametrizar com algumas informações. Veja os do Jboss 6 aqui por exemplo: http://docs.jboss.org/jbosssecurity/docs/6.0/security_guide/html/Login_Modules.html

      Aqui no Brasil, o pessoal do framework Demoiselle tem um componente independente que é um LoginModule básico que você só precisa estender e implementar dois ou três métodos para ter um esquema de user role efetivo. http://demoiselle.sourceforge.net/component/demoiselle-security/1.2.1/docs.html

      Fora isso, tente ler algo sobre como configurar segurança JAAS no web.xml de cada aplicação, que basicamente é programar que padrão de URL ficará escondido, e que permissões é preciso ter para acessar essas URL’s. Outro detalhe importante é pesquisar como fazer sua aplicação depender de um serviço JAAS configurado no servidor. No caso do Jboss é preciso incluir um arquivo jboss-web.xml (além do web.xml) na aplicação. Isso cada servidor tem sua forma de trabalho!

      Boa sorte!

  3. Boa tarde Rafael.
    Hoje nossas aplicações realizam a autenticação usando um form que submete os dados via POST para a url “j_security_check”, como os valores de j_username e j_password. Portanto, estamos tendo a necessidade de autenticar de forma programática, ou seja, sem usar o formulário de login. A principio pensamos em criar um método em nosso Managed Bean (MB) correspondente a tela inicial da aplicação, que receba o usuário e senha e envie os dados para a url “j_security_check”. Quando chamamos o método logar de nosso MB a partir da ação de um formulário de login, conseguimos realizar a autenticação normalmente, porém quando precisamos acessar o método logar a partir de outro método do nosso MB, não obtemos sucesso. Segue o método logar de nosso MB.

    public void logar() throws ServletException, IOException {
    request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
    response = (HttpServletResponse) facesContext.getExternalContext().getResponse();

    RequestDispatcher dispatcher = request.getRequestDispatcher(“/j_spring_security_check”);
    dispatcher.forward(request, response);

    facesContext.responseComplete();
    }

    Antecipadamente, fico muito grato pela atenção.

  4. Olá,
    no caso desse post, não tem a configuração dos modules options no security domain. No jboss se usava para ao j security check, os padrões

    Para este caso de login com servlets, deve definir esses parâmetros no security domain?

    Obrigado, muito bom o post.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *