Vamos ver nesse artigo como podemos customizar o nosso login para deixá-lo mais profissional e adequado a um ambiente de produção. Para conferir como podemos criar uma aplicação rapidamente com Spring Security disponibilizamos um link nas referências bibliográficas indicando o artigo.

Entre as customizações que serão realizadas destacam-se:

  • Como a página de login padrão do Spring Security é muito genérica e não tem uma aparência semelhante com o resto da aplicação será necessário um formulário de login integrado com o restante da aplicação.
  • Devemos fornecer uma forma do usuário fazer logout na aplicação.
  • Precisamos refinar mais os roles (papeis) para que possamos tornar algumas páginas acessíveis a todos e outras que necessitem de autenticação. Também dividiremos a nossa aplicação em dois tipos de papéis, um para usuários comuns e outro para administradores.
  • Devemos exibir algumas informações de contexto indicando se o usuário está autenticado e o nome de usuário autenticado.

No restante do artigo veremos como podemos efetuar as customizações numa aplicação web.

Customizando o Login da Aplicação

Primeiramente devemos atualizar o arquivo security.xml, conforme exemplificado na Listagem 1.


<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/security 
    http://www.springframework.org/schema/security/spring-security.xsd">
                                         


 <http auto-config="true" use-expressions="true">
       
       <intercept-url pattern="/" access="permitAll"/>
       <intercept-url pattern="/login/*" access="permitAll"/>
       <intercept-url pattern="login.jsp" access="permitAll"/>
       <intercept-url pattern="/logout.jsp" access="permitAll"/>
       <intercept-url pattern="/index.jsp" access="hasRole('ROLE_USER')"/>
       
       
       <form-login login-page="/login.jsp"
              login-processing-url="/fazerLogin"
              authentication-failure-url="/login.jsp?error"
              default-target-url="/index.jsp"/>
              
       <logout logout-url="/logout" logout-success-url="/login.jsp?logout"/>
       
 </http>


 <authentication-manager>
       <authentication-provider>
              <user-service>
              
                     <user name="user1@example.com"
                     password="user1"
                     authorities="ROLE_USER"/>
                     
                     <user name="admin1@example.com"
                     password="admin1"
                     authorities="ROLE_USER,ROLE_ADMIN"/>
                     
              </user-service>
       </authentication-provider>
 </authentication-manager>
 
</beans:beans>
Listagem 1. Exemplo do arquivo security.xml atualizado com novos elementos

Como novidades no arquivo security.xml, temos na tag em o atributo login-page que especifica onde o Spring Security redirecionará o browser quando uma página protegida for acessada e o usuário não estiver autenticado. Caso uma página de login não for especificada, o Spring Security irá redirecionar o usuário para a url padrão /spring_security_login. Dessa forma, org.springframework.security.web.filter.FilterChainProxy escolherá o org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, na qual exibirá a página de login padrão. DefaultLoginPageGeneratingFilter está configurado para processar /spring_security_login. Como sobrescrevemos a URL default, somos responsáveis pela página de login quando a URL /login.jsp for solicitada.

O atributo login-processing-url tem como padrão /j_spring_security_check. Este atributo especifica a URL que o formulário de login (que deve incluir o nome de usuário e senha) deve ser submetido, usando um HTTP POST. Quando o Spring Security processa esta solicitação, ele tentará autenticar o usuário. Alteramos o valor padrão para /fazerLogin.

Os atributos username-parameter e password-parameter têm como padrão j_username e j_password respectivamente e especifica os parâmetros HTTP que o Spring Security usará para autenticar o usuário quando processar login-processing-url. Para este código não foram adicionados esses atributos, dessa forma, nosso formulário deverá ter os valores padrão.

O atributo authentication-failure-url especifica a página que o Spring Security redirecionará se o username e password submetido por login-processing-url for inválido.

É sempre uma boa prática sobrescrevermos todos esses atributos como forma de não expor que estamos supostamente usando o Spring Security. Revelar o framework que estamos usando é um tipo de "vazamento de informações", tornando mais fácil para os atacantes determinar potenciais falhas na segurança de uma aplicação. Portanto, apenas estamos utilizando o username e password padrão para fins didáticos, para que o leitor possa verificar que se não especificarmos esses atributos com seus valores de forma explícita deveremos usar os valores definidos por default.

Agora podemos definir o nosso login customizado. A JSP da Listagem 2 demonstra um exemplo de uma página de login.


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<html>
 <head>
       <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
       <title>Página de Login Customizado</title>
 </head>
 
 <body>
 
   <!-- HEADER -->
                 
   <div>
    <ul class="nav">
     
     <sec:authorize access="authenticated" var="authenticated"/>
     <c:choose>
        <c:when test="${authenticated}">
         <li id="greeting">
            <div>
              Bem-vindo
              <sec:authentication property="name" />
            </div>
         </li>

         <li>
            <a href="/ExemploSpringSecurity/logout">Logout</a>
         </li>
        </c:when>
        <c:otherwise>
         <li>
         <a id="navLoginLink" href="/login.jsp">Login</a>
         </li>
        </c:otherwise>
     </c:choose>
            
    </ul>
   </div>
                 
   
   
   <!-- FORMULÁRIO -->
   
   <form name="f" action="<c:url value='/fazerLogin'/>" method="post">
   
      <c:if test="${param.error != null}">
       <div class="alert alert-error">
        Falha ao fazer Login.
        <c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}">
              Motivo: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />
        </c:if>
       </div>
      </c:if>

      <c:if test="${param.logout != null}">
       <div class="alert alert-success">
          Você efetuou log out.
       </div>
      </c:if>

      <label for="username">Username</label>
      <input type="text" id="j_username" name="j_username"/>
      <label for="password">Senha</label>
      <input type="password" id="j_password" name="j_password"/>

      <div class="form-actions">
       <input id="submit" class="btn" name="submit" type="submit" value="Login"/>
      </div>
          
   </form>
       
       
 </body>
</html>
Listagem 2. Exemplo de página JSP para login, logout e mensagens

Existem alguns itens importantes no código acima, entre eles destacam-se:

  • Podemos verificar que o atributo action do formulário deve ser igual ao valor de login-processing-url no arquivo security.xml.
  • Por segurança o Spring Security apenas processa requisições usando POST.
  • Usamos param.error para verificar se há algum problema com o login. Para isso utilizamos o atributo authentication-failure-url contendo um parâmetro de erro do HTTP (/login.jsp?error).
  • O SPRING_SECURITY_LAST_EXCEPTION é um atributo de sessão que contém a última exceção do org.springframework.security.core.AuthenticationException, que pode ser usado para exibir informações sobre o motivo da falha do login. As mensagens de erro do Spring Security também podem ser customizadas através do suporte à internacionalização disponibilizado pelo Spring.
  • Os campos de input para username e password são escolhidos de acordo com o que foi definido nos atributos username-parameter e password-parameter no arquivo de configuração security.xml. Como não definimos nenhum devemos usar o nome padrão.

Segue na Listagem 3 o arquivo web.xml que estamos utilizando e já foi explicado no primeiro artigo sobre Spring Security (link nas referências bibliográficas).


<?xml version="1.0" encoding="UTF-8"?>
<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_2_5.xsd" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" 
version="2.5">
  <display-name>Exemplo Spring Security</display-name>
  <welcome-file-list>
    <welcome-file>/index.jsp</welcome-file>
  </welcome-file-list>
  
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/security.xml</param-value>
  </context-param>
  
  <listener>
    <listener-class>org.springframework.web.context
    .ContextLoaderListener</listener-class>
  </listener>
  
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter
    .DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  
</web-app>
Listagem 3. Exemplo do arquivo web.xml utilizado

Assim sendo, com esses três arquivos já temos nossa aplicação pronta para rodar com um login customizado.

Podemos notar também que o nosso login customizado possui suporte para logout que é muito simples de ser criado visto que a configuração do Sprint Security adiciona suporte automático para esta funcionalidade. Dessa forma, precisamos apenas criar um link que aponta para /j_spring_security_logout. O logout também pode ser customizado no arquivo security.xml. Para criar o logout apenas adicionamos a linha abaixo no arquivo security.xml dentro da tag http:


<logout logout-url="/logout" logout-success-url="/login.jsp?logout"/> 

Agora basta adicionar também um link para o usuário clicar e efetuar o logout da aplicação. Basta adicionarmos o código abaixo na nossa JSP:


<a href="/ExemploSpringSecurity/logout">Logout</a>

Para facilitar. também criamos uma mensagem indicando se o usuário fez logout. Nesse caso, quando o usuário faz logout é enviado um parâmetro logout, portanto basta verificarmos se o parâmetro foi recebido no carregamento da página. Segue na Listagem 4 o código que utilizamos na aplicação.


<c:if test="${param.logout != null}">
   <div class="alert alert-success">
         Você efetuou log out.
   </div>
</c:if>
Listagem 4. Exemplo de como exibir uma mensagem quando o usuário faz logout

O Spring Security também permite que possamos colocar múltiplos elementos permitindo um melhor controle de diferentes porções da aplicação. Cada elemento http é considerado na ordem e a primeira combinação será realizada. Portanto, a ordem é muito importante.

Uma configuração muito importante que foi feita no arquivo security.xml é no onde definimos o . Essa configuração permite um controle mais granular sobre como os recursos podem ser acessados. Segue na Listagem 5 a configuração realizada.


<intercept-url pattern="/" access="permitAll"/>
<intercept-url pattern="/login/*" access="permitAll"/>
<intercept-url pattern="login.jsp" access="permitAll"/>
<intercept-url pattern="/logout.jsp" access="permitAll"/>
<intercept-url pattern="/index.jsp" access="hasRole('ROLE_USER')"/>
Listagem 5. Exemplo de um maior controle sobre os recursos da aplicação usando intercept-url

Nesse caso permitimos acesso total ao recurso de login, logout e restringimos acesso à página index.jsp apenas para usuários logados e que tenham o papel ROLE_USER que também está definido no arquivo security.xml. O pattern /login/* foi adicionado caso tenhamos algum servlet que faça alguma interceptação, caso não tenhamos ele não é necessário. É muito importante darmos permissão para todos acessarem as páginas de login e logout ou a aplicação pode entrar em loop e o navegador exibirá uma página de erro.

Devemos notar também que o atributo use-expressions está setado para true, nesse caso estamos usando a Spring Expression Language (SpEL) para determinar se um usuário tem autorização. Caso ele não esteja marcado como true, deveríamos usar conforme a Listagem 6.


<intercept-url pattern="/" access="ROLE_ANONYMOUS,ROLE_USER"/>
<intercept-url pattern="/login.jsp" access="ROLE_ANONYMOUS,ROLE_USER"/>
<intercept-url pattern="/logout.jsp" access="ROLE_ANONYMOUS,ROLE_USER"/>
<intercept-url pattern="/index.jsp" access="ROLE_USER"/>
Listagem 6. Exemplo não utilizando expressões

Nesse caso ROLE_ANONYMOUS indica um usuário que não está logado e tem acesso total ao recurso.

Na aplicação que foi criada também utilizamos uma tag library para exibir informações sobre o usuário autenticado. Para isso tivemos que incluir a tag library chamada spring-securitytaglibs-3.1.0.RELEASE.jar na nossa aplicação.

Para utilizarmos essa tag library tivemos que incluir a diretiva abaixo na nossa JSP:


<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

E no corpo da nossa página JSP utilizamos também:


<sec:authorize access="authenticated" var="authenticated"/>

e também:


<sec:authentication property="name" />

A tag determina se o usuário está autenticado ou não e atribui este valor para a variável authenticated. A tag procura pelo objeto org.spring.security.core.Authentication atual. O atributo property irá procurar o atributo "principal" em org.spring.security.core.Authentication, que neste caso é do tipo org.spring.security.core.userdetails.UserDetails. Este por sua vez obtém a propriedade username em UserDetails e exibe este valor na página.

Outra configuração interessante para uma aplicação é customizarmos o comportamento da aplicação após o login. O atributo default-target-url do elemento define para onde a aplicação será redirecionada após um login ser feito com sucesso. Se default-target-url estiver indefinido seremos redirecionados para a raiz da aplicação.

Vale ressaltar que se um usuário solicitar uma página protegida, antes de ser autenticado, o Spring Security vai lembrar-se da última página protegida que foi acessada antes de autenticar. Para isso o Spring Security usa o org.spring.security.web.savedrequest.RequestCache. Portanto, após a autenticação ser bem sucedida, o Spring Security irá enviar o usuário para a última página protegida que foi acessada antes da autenticação. Por exemplo, se um usuário não autenticado solicita a página teste.jsp, ele será enviado para a página de login, para que o usuário possa ser autenticado antes de acessar o recurso protegido. Após o usuário ser autenticado com sucesso, o usuário será enviado para a solicitação anteriormente solicitada, ou seja, a página teste.jsp.

Se quisermos que o usuário sempre seja redirecionado para o default-target-url podemos setar o atributo alwaysuse-default-target para true. Segue abaixo um exemplo:


<form-login ...
   always-use-default-target="true"/>

Executando a aplicação

Na Figura 1 vemos como ficou a estrutura completa da aplicação.

img
Figura 1. Estrutura da aplicação

Ao executar a aplicação temos primeiramente a tela de login sendo exibida ao usuário, solicitando suas credenciais. Veja a tela da Figura 2.

img
Figura 2. Tela de login

A tela de login foi exibida, pois a aplicação tentou acessar a index.jsp, porém para acessar essa página o usuário precisa de autorização, conforme configuramos no arquivo security.xml. Se digitarmos um login errado, como exibido na Figura 3. teremos a seguinte tela sendo exibida.

img
Figura 3. Mensagem de falha ao fazer login é exibida

Se colocarmos o username e senha corretos somos redirecionados para a tela solicitada, ou seja, para a página index.jsp. A Figura 4 mostra a tela.

img
Figura 4. Tela requisitada exibida com sucesso

Se visitarmos novamente a tela de login, veremos que duas novas opções serão exibidas. Uma das opções é para que possamos fazer logout e a outra é o nome do usuário atualmente logado na aplicação. A Figura 5 mostra a tela.

img
Figura 5. Informações do usuário e opção de logout são exibidas para o usuário autenticado

Essas informações também poderiam estar disponíveis em toda a aplicação, num cabeçalho ou num menu fixo.

Dessa forma, nossa aplicação já possui um login customizado e executando no navegador do usuário.

Bibliografia:
  1. Spring Security, disponível em projects.spring.io/spring-security
  2. Robert Winch, Peter Mularien. Spring Security 3.1.. Packt Publishing, 2012.
  3. Mick Knutson. Java EE 6 Cookbook for Securing, Tuning and Extending Enterprise Applications. Packt Publishing, 2012.
  4. Spring Security 3.1: Configuração e utilização em um exemplo web.