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>
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>
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>
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>
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')"/>
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"/>
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.
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.
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.
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.
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.
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.
- Spring Security, disponível em projects.spring.io/spring-security
- Robert Winch, Peter Mularien. Spring Security 3.1.. Packt Publishing, 2012.
- Mick Knutson. Java EE 6 Cookbook for Securing, Tuning and Extending Enterprise Applications. Packt Publishing, 2012.
- Spring Security 3.1: Configuração e utilização em um exemplo web.