Autenticação de Usuários (Filter/Servlet)

Olá, tudo bem?

Vamos falar hoje sobre como controlar autenticações de usuário de modo manual? Para fazermos isso, iremos utilizar uma ferramenta do Java bastante simples e poderosa – Filtro (Filter). Será usado como base o código desenvolvido nos outros post sobre Web Applications: Criando um WebServer, Tratando Exceções.

Vou colocar abaixo o código (resumido) que iremos utilizar, será um arquivo do tipo Servlet, dois arquivos JSPs e o arquivo web.xml (apenas com esses arquivos conseguiremos colocar o Filtro em uso). Caso tenha problemas em apenas criar um projeto web com o código abaixo, olhe esse link que mostra um passo a passo: Criando um WebServer (com esses três arquivos você já será capaz de iniciar um webserver e navegar).
Nosso projeto se chama Servlet. O arquivo web.xml ficará dentro da pasta WebContent/WEB-INF

<?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>Servlet</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>  
    </welcome-file-list>
  
    <servlet>
        <servlet-name>hello</servlet-name>
        <jsp-file>/index.html</jsp-file>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    
    <servlet>
        <servlet-name>final</servlet-name>
        <servlet-class>com.Servlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>final</servlet-name>
        <url-pattern>/mathit</url-pattern>
    </servlet-mapping>     
</web-app>

Código do arquivo com.Servlet.java

package com;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Servlet extends HttpServlet {
    public void doPost(HttpServletRequest req, HttpServletResponse res)    throws ServletException, IOException {
        int value1;
        int value2;
        int total = 0;
        String name = (String) req.getParameter("name");
        String warning = "";
        
        try {
            value1 = Integer.parseInt((String) req.getParameter("value1"));
            value2 = Integer.parseInt((String) req.getParameter("value2"));
        } catch (NumberFormatException e) {
            value1 = 0;
            value2 = 0; 
            warning = "We got some bad value(blank or non numerics values, we set 0 instead";
        }
        
        req.setAttribute("name", name);
        req.setAttribute("warning", warning);
        
        total = value1 + value2;
        
        req.setAttribute("total", total);
        
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("/WEB-INF/show.jsp");
        
        requestDispatcher.forward(req, res);
    }
}

Código do arquivo /WebContent/index.html

<html>
    <body>
        <form action="/Servlet/mathit" method="post">
            <h1>Hello</h1>
            Type your name: <input type="text" name="name"/> <br/><br/>
            Lets do some math?<br/>
            Type the first value to be added: <input type="text" name="value1"/> <br/>
            Type the second value to be added: <input type="text" name="value2"/> <br/><br/>
            <input type="submit" value="Do some math"/>
        </form>
    </body>
</html>

Código do arquivo /WebContent/WEB-INF/show.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <body>
        Thanks ${name} for passing by. <br/>
        The total is: ${total}. <br/>
        
        <br/>${warning}
    </body>
</html>

Para acessar, basta iniciar o Tomcat e acessar pelo endereço: http://localhost:8080/Servlet/hello.

O Filtro é uma classe do Java que pode interceptar as requisições feitas, apenas por mapear um padrão de requisição (URL, Servlet, etc…). Para começar, vamos criar uma classe de filtro e colocá-la para trabalhar.
O código da nossa classe filtro ficará assim:

package com;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * Servlet Filter implementation class UserCheckFilter
 */
public class UserCheckFilter implements Filter {
    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {
    }

    /**
     * @see Filter#destroy()
     */
    public void destroy() {
    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        System.out.println("Hello World");
        
        chain.doFilter(request, response);
    }
}

Note que o método init e destroy pertencem à interface Filter e devem ser implementados. Estamos apenas exibindo “Hello World” para certificar que nosso filtro esteja correto e funcionando.

Temos agora que adicionar as seguintes tags ao arquivo web.xml:

<filter>
    <filter-name>UserCheckFilter</filter-name>
    <filter-class>com.UserCheckFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>UserCheckFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Explicando as tags: “url-pattern” define a url que estaremos mapeando, e em nosso caso estamos capturando qualquer url que for solicitada. “filter-name” define o nome do filtro mapeado que será acionado. O “filter-class” é a nossa classe Java que já foi criada.

Inicie o Tomcat e acesse pelo link: http://localhost:8080/Servlet/hello (caso você tenha problemas em visualizar a página, visite o primeiro post para tirar suas dúvidas: Criando um WebServer)
Olhe no console e veja que a nossa mensagem irá aparecer lá.
Hello World Screen

Com nosso filtro em pleno funcionamento, vamos criar uma classe de usuário. Um POJO simples, contendo nome do usuário e senha. Nossa classe ficará assim:

package com;

public class User {
    private String name = "UNKNOW";
    private String password = "UNKNOW";
    
    public User(String name, String password){
        this.name = name;
        this.password = password;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

Precisamos de um Servlet para fazer a validação de quem está acessando nosso servidor, vamos criá-lo de modo que valide nome/senha do usuário. Vamos criar nosso Servlet com um método para fazer essa validação (não vamos entrar em detalhes de patterns no momento).

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class UserValidator extends HttpServlet {
    private static final Map users = getUsers();
    
    /**
     * Creates valid users 
     * 
     * This User Map could be users returned from a database
     * or a simple select with the user.name
     * 
     * @return a Map of valid users
     */
    private static Map getUsers() {
        Map users = new HashMap();
        
        User userOne = new User("one","one");
        User userTwo = new User("two","TWO");
        
        users.put(userOne.getName(), userOne);
        users.put(userTwo.getName(), userTwo);
        
        return users;
    }

    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        doPost(req, res);
    }

    public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        RequestDispatcher rd;
        String name = req.getParameter("name");
        String password = req.getParameter("password");
    
        User user = validateLogin(name, password);
        
        if (user == null){
            rd = req.getRequestDispatcher("/loginError.jsp");
        }
        else{
            HttpSession session = req.getSession();
            session.setAttribute("user", user);
            rd = req.getRequestDispatcher("/loginSuccess.jsp");
        }
        
        rd.forward(req, res);
    }

    /**
     * Validate the entered data
     * 
     * If there is no valid data, the method will return null
     * 
     * @param name given at the jsp
     * @param password given at the jsp
     * @return a user if one was found and validated
     */
    private User validateLogin(String name, String password) {
        // All parameters must be valid
        if (name == null || password == null){
            return null;
        }
        
        // Get a user by key
        User user = users.get(name);
        
        if (user == null){
            return null;
        }
        
        // Check if the password is valid
        if (!user.getPassword().equals(password.trim())){
            return null;
        }
        
        return user;
    }
}

O Map “users” e a função “getUsers” existem apenas para gerar usuários válidos. Caso nosso usuário não tenha seus dados válidos o método “validateLogin” retorna o valor null. Dependendo dos dados fornecidos pelo usuário, caso válido ele será redirecionado para a página de sucesso, caso contrário, irá para a página de erro.

Precisamos alterar o arquivo “web.xml” e adicionar o mapeamento do Servlet que acabamos de criar:

<servlet>
    <servlet-name>userValidator</servlet-name>
    <servlet-class>com.UserValidator</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>userValidator</servlet-name>
    <url-pattern>/userValidator</url-pattern>
</servlet-mapping>

Vamos criar 3 arquivos do tipo JSP, para: login, erro no login e login com sucesso:
/WebContent/login.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <title>Login</title>
    </head>
    <body>
        <form action="/Servlet/userValidator" method="post">
            Name: <input type="text" name="name"/>
            Password: <input type="password" name="password"/>
            <input type="submit" value="LogIn"/>
        </form>
    </body>
</html>

/WebContent/loginError.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <title>Error</title>
    </head>
    <body>
        Wrong user/password
    </body>
</html>

/WebContent/loginSuccess.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <title>Success</title>
    </head>
    <body>
        You have loged in.
    </body>
</html>

Precisamos alterar agora nosso Filtro. Essa será a alteração mais delicada, pois será nosso filtro que irá definir se nosso usuário pode continuar em sua requisição ou não.

package com;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * Servlet Filter implementation class UserCheckFilter
 */
public class UserCheckFilter implements Filter {
    private String LOGIN_ACTION_URI;
    
    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {
        LOGIN_ACTION_URI = fConfig.getInitParameter("loginActionURI");
    }

    /**
     * @see Filter#destroy()
     */
    public void destroy() {
    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute("user");
        
        if (user == null && !LOGIN_ACTION_URI.equals(req.getRequestURI())){
            RequestDispatcher rd = req.getRequestDispatcher("/login.jsp");
            rd.forward(request, response);
            return;
        }
        
        chain.doFilter(request, response);
    }
}

Alguns detalhes sobre o código acima: “req.getSession()” – retorna a sessão do usuário, mas caso não tenha nenhuma sessão o próprio container fica encarregado de criar uma. Buscamos dentro da sessão o objeto usuário, em nosso servlet ao efetuar o login um objeto do tipo User é inserido na sessão. Caso não exista um objeto do tipo User na sessão, o valor retornado é null e com isso direcionamos o usuário para a tela de login. Note também que temos um atributo LOGIN_ACTION_URI que é retornado das configurações do filtro, ele será configurado daqui a pouco no arquivo web.xml. O atributo LOGIN_ACTION_URI existe para caso o usuário esteja tentando realizar a validação ele possa chegar ao nosso Servlet de validação de usuário, caso esse atributo não existisse, não seria possível realizar o login pois o objeto User nunca seria adicionado à sessão.

Falta agora colocar o parâmetro de inicialização do filtro no arquivo web.xml:

<filter>
    <filter-name>UserCheckFilter</filter-name>
    <filter-class>com.UserCheckFilter</filter-class>
    <init-param>
        <param-name>loginActionURI</param-name>
        <param-value>/Servlet/userValidator</param-value>
    </init-param>
</filter>

Ao acessar o link http://localhost:8080/Servlet/hello, o usuário será redirecionado para a página de login. Após digitar um usuário/senha que sejam válidos, ele terá sua sessão validada. E não será mais necessário realizar essa validação.

Caso tenha alguma dúvida/colocação a fazer, basta postar.

Até a próxima.

31 thoughts on “Autenticação de Usuários (Filter/Servlet)

  1. Muito bem explicado, parabéns, estava penando atrás de um exemplo assim, vou implementar para testar, muito interessante o uso do atributo LOGIN_ACTION_URI.

  2. Olá, implementei o filtro, porém ao invés de usar o atributo LOGIN_ACTION_URI coloquei as páginas com acesso restrito em uma pasta “restrito” na raiz da aplicação e no web.xml coloquei:

    FiltroDeAcesso
    br.com.filtro.FiltroDeAcesso

    FiltroDeAcesso
    /restrito/*

    no filtro:

    RequestDispatcher rd = req.getRequestDispatcher("/login.jsp");
    rd.forward(request, response);
    return;

    A página login.jsp está na raiz do projeto…quando tento acessar diretamente pelo browser uma página dentro da pasta restrito, exemplo:
    http://localhost:8080/agenda/restrito/BemVindo.jsp
    o filtro é invocado e redireciona para a página login.jsp, porém o css da página login não é carregado, saberia me responder o porque?

  3. Hebert,

    **Favor apagar o comentario anterior. Corrigi alguns erros e adicionei uns comentarios nos comentarios (coloquei em caixa alta para destacar).

    Belo post, porem fiquei com três dúvidas…

    Meu servlet deu erro na classe UserValidator e até o eclipse apontou algums erros.

    Segui o mesmo código que vc mostrou, só removendo os comentarios.

    -PRIMEIRO

    /**
    * Logo no começo do código ele aprenta os seguintes erros:
    * Multiple markers at this line
    * – Syntax error on token “=”, != expected
    * – Syntax error on token “,”, TypeArgument1 expected after this
    * token —> ESSE ERRO VEM DA VÍRGULA DEPOIS DE STRING
    * – Return type for the method is missing
    * – Duplicate method getUsers() in type UserValidator
    * – This method requires a body instead of a semicolon
    */
    private static final Map users = getUsers();

    /**
    * Na linha abaixo da ultimo são esses os erros:
    * Multiple markers at this line
    * – Return type for the method is missing
    * – Duplicate method getUsers() in type UserValidator
    * – Syntax error on token “,”, TypeArgument1 expected after this
    * token —> ESSE ERRO VEM DA VÍRGULA DEPOIS DE STRING
    * – Syntax error, insert “;” to complete FieldDeclaration
    */
    private static Map getUsers() {
    /**
    * Outros erros na linha seguinte…
    * Multiple markers at this line
    * – Syntax error on token “=”, != expected
    * – Syntax error on token “,”, TypeArgument1 expected after this
    * token
    * – Syntax error on tokens, delete these tokens –> ESSE ERRO VEM DO user=””
    */
    Map users = new HashMap();

    -SEGUNDA

    Tambem no final o eclipse aponta um erro no método validateLogin…

    //users cannot be resolved
    User user = users.get(name);

    -TERCEIRA

    Outra dúvida que tive foi com relação a última linha. O que significa aquele trecho onde você colocou “”?

    Eu tive que tirar isso pois o eclipse acusava que a classe precisava de uma chave “}” de fechamento.

    Desculpa a quantidade de perguntas.

    • Opa, beleza?

      As respostas para suas perguntas são as mesmas.
      Realmente não sei te falar. Teria que ver qual o erro, e o código.

      Você é o primeiro que aponta esses erros e eu não consigo te falar assim.

      Teria como você postar no GUJ o código e o erro? Caso eu não responda, alguém o fará.

      Me perdoe a demora em responder, realmente estou muito ocupado nesses dias. =/

    • Wesley, bom dia.

      Tem sim. Uma solução seria salvar no banco de dados se o usuário está ativo ainda, se sim, você marcaria esse campo como true.

      O x da questão é que a pessoa pode simplesmente fechar o browser e não apertar o logout que vc criar para sua aplicação.

      Para esse problema bastaria vc ver o tempo da ultima navegação/utilização da aplicação por aquele usuário.

      Obrigado pela visita.

      Me perdoe a demora em responder, realmente estou muito ocupado nesses dias. =/

  4. Opa excelente marial hebert parabens pelo blog.
    Nao teria como voce fazer um tutorial mostrando como se trabalhar com o spring security?
    =)

    • Renato, bom dia.

      Infelizmente meu conhecimento não é bom com Spring.
      Tá melhorando pois aos poucos eu tenho estudado, mas esse ano ainda pretendo lançar posts sobre ele.

      Obrigado pela visita.

      Me perdoe a demora em responder, realmente estou muito ocupado nesses dias. =/

  5. Só me faz um favor antes, só uma tentativa.

    Usando o JDK mais recente, coloca o código da classe UserValidator no seu Eclipse e vê se ele mostra algum erro.

    Acho que já vai te ajudar a entender.

    Caso não seu Eclipse não aponte nenhum erro eu tento postar no GUJ.

    • Jaison, bom dia.

      Infelizemnte não tenho o código rodando aqui agora.

      E estou realmente corrido, estou trabalhando agora para você ter idéia. [=

      Inclusive nas minhas férias estive trabalhando, então vc já viu a correria né?

      Obrigado pela visita. o_

    • bel, bom dia.

      Basta adicionar as páginas dentro da pasta que o JAAS está deixando segura. [=

      Obrigado pela visita.

  6. Viva,
    alguem me consegue ajudar, estou configurando o servidor e consegui fazer todas as correçoes só que agr tenho uma em falta

    private User validateLogin(String name, String password) {
    // All parameters must be valid
    if (name == null || password == null){
    return null;
    }

    // Get a user by key
    //ERRO na linha seguinte
    //Type mismatch: cannot convert from Object to user -> userValidator.java

    User user = users.get(name);

    if (user == null){
    return null;
    }

    // Check if the password is valid
    if (!user.getPassword().equals(password.trim())){
    return null;
    }

    return user;

  7. Olá Hebert, onde fica a configuração para bloquear os diretório das páginas, o que entendi é que o Filter bloqueia um diretório de páginas onde só pode entrar depois de logado. tenho uma aplicação e quero aplicar um filtro para autenticação, mas as páginas ficam em várias pastas da aplicação e todas devem ser boqueadas pelo Filter. tem como?

    • Alexsandro,

      Fica dentro do próprio filter. A pasta a ser bloqueada é ‘simbolizada’ pela URL.

      Uma vez que a URL seja bloqueada a pasta está protegida.

  8. Olá, parabéns pelo post, consegui implementar a autenticação porém estou com uma dúvida.

    Caso eu logue com os dois usuário ao mesmo tempo, a sessão saberá qual é o ativo, apenas usando o atributo user?

    Gostaria de saber se posso implementar uma consulta no banco de usuários e logar com vários ao mesmo tempo sem que nenhum perca a sessão.

    Obrigada.

  9. Ola eu estou tentando fazer msa está dando erro no Map. Lista de erros:
    Em private static final Map users = getUsers();
    Erro: Criar class user no pacote com; Criar campo users em com.UserValidator
    E
    Em: private static Map getUsers() {
    Erro: criar class user no pacote com ; Criar metodo getUsers() em com.UserValidator

    e por assim vai um monte de erro, se eu mudo o user para User que é o nome da classe
    ele contiuar com os erros para criar os metodos que estao ali.

    • Sane, boa noite.

      Infelizmente não consigo imaginar qual seria o erro, a mensagem que você me passou está muito genérica.

      Obrigado pela visita.

  10. Quando o site ainda está carregando o código difere de após ter carregado porque isso?
    e não seria: Map<String, User> users = new HashMap<String, User>();?
    e pra que estes no final da classe UserValidator?
    e deu uma exception:
    java.lang.ClassCastException: com.UserValidator cannot be cast to javax.servlet.Filter

    • Diego, boa tarde.

      Não sei te falar por que desse erro uma vez que seguindo o código passo a passo não acontece, pelo menos você é o primeiro a falar reportar isso.

      No post está com Map users por causa de erro em formatação. -_-”

      getUsers() é um método que você deve criar para gerar usuários válidos. Se você quiser fazer na mão, pode fazer.

      Vou consertar depois. [=

      Obrigado pela visita.

  11. Boa noite Uaihebert,

    É um prazer estar no seu site, a procura de uma implantação de autenticação, acabei passando por aqui.
    Bom, implementei os cdigos passados acima e me deparei com um erro que é o seguinte…..

    Quando digito o usuario e senha ele vai direto para pagina loginErro, ou seja nao autentica o usuario, ele esta buscando meu usuario como NULL ! Entao fui na classe UserValidator e troquei a condição para buscar usuario NULL, apenas para teste.. Entao ele foi para a tela de LoginSucess, porém nao me redirecionou para a página esperada….. Poderia me auxiliar no que fazer ?? No aguardo e mais uma vez Obrigado

    • Wallace, boa tarde.

      Infelizmente não tenho como dizer o que está errado pois não sei o que aconteceu de errado/certo em sua alteração.

      Obrigado pela visita, e desculpe a demora em responder. Estive muito ocupado nesse último mes.

Leave a Comment