Trong một bài viết trước tôi đã có giới thiệu về Servlet và hướng dẫn một bài mẫu để các bạn tạo trang đăng nhập bằng Servlet. Tôi dự dịnh sẽ viết một bài kết hợp giữa Servlet, JSP, JDBC nhưng cảm thấy vẫn chưa đủ tài liệu nên viết bài này để hướng dẫn các bạn sử dụng Filter trong Servlet. Mục đích là để các bạn hiểu được Filter trước khi tôi mang nó vào trong bài viết sau đó.

Servlet Filter là gì?

Servlet Filters là các lớp Java được sử dụng trong lập trình Servlet với những mục đích sau:
  • Dùng để chặn các request từ phía client trước khi nó có thể truy cập đến các tài nguyên ở phía Back-end
  • Thao túng các response từ phía server trước khi nó trả về cho client
Servlet Filter

Bạn có thể hình dung một cách đơn giản như thế này nhé. Bạn muốn người dùng phải đăng nhập trước khi truy cập vào các trang quản lý sản phẩm. Lúc này, Filter nó sẽ chặn các request đến trang quản lý sản phẩm để kiểm tra xem phía client đã đăng nhập hay chưa.

Có khá nhiều kiểu Filter được đề xuất như sau:
  • Authentication Filters
  • Data compression Filters
  • Encryption Filters
  • Filters that trigger resource access events
  • Image Conversion Filters
  • Logging and Auditing Filters
  • MIME-TYPE Chain Filters
  • Tokenizing Filters
  • XSL/T Filters That Transform XML Content

Như tôi vừa đề cập ở trên thì việc yêu cầu người dùng đăng nhập trước khi truy cập vào phần quản lý sản phẩm là Authentication Filter. Cũng chính vì lý do này mà tôi viết ra bài này để hướng dẫn các bạn sử dụng Filter thay vì phải kiểm tra thông tin mỗi khi client gửi request đến một Servlet nào đó.

Thực hành cách sử dụng Filter trong Servlet

Cách tạo Filter
Tất cả các Filter khi được tạo ra phải thực thi javax.servlet.Filter interface. Interface này định nghĩa cho chúng ta ba phương thức abstract mà chúng sẽ được gọi ở những thời điểm khách nhau trong 1 vòng đời của filter.
  • public void init(FilterConfig config)
  • public void doFilter(ServletRequest req, ServletResponseresp, FilterChain chain)
  • public void destroy()

Dự án mẫu về cách sử dụng Filter

Vì hướng dẫn này tôi chỉ tập trung vào hướng dẫn các bạn sử dụng Filter, vì vậy nên các dữ liệu cần thiết sẽ được tôi hard-code. Những bài viết tiếp theo khi sử dụng JDBC và CSDL thì chúng ta sẽ làm khác nhé.

Đầu tiên, các bạn tạo dự án web bằng maven, import vào eclipse và tạo một cấu trúc như sau:
Hướng dẫn sử dụng Filter trong Servlet
Xem lại bài viết Tạo trang đăng nhập bằng Java Servlet + JSP + Maven để biết cách tạo dự án.
Nội dung tập tin pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.vanthuong</groupId>
  <artifactId>filter-tutorial</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>filter-tutorial Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <!-- Servlet 3.0 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
    </dependency>

    <!-- JSP -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.1</version>
      <scope>compile</scope>
    </dependency>

    <!-- JSTL -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>filter-tutorial</finalName>
  </build>
</project>

UserInfo.java
package com.vanthuong.tutorial.filter;

public class UserInfo {
 public static final String username = "admin";
 public static final String password = "123456";
}

LoginServlet.java
package com.vanthuong.tutorial.servlet;

import java.io.IOException;

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

import com.vanthuong.tutorial.filter.UserInfo;

@WebServlet("/login/")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        RequestDispatcher dispatcher = req.getRequestDispatcher("/static/views/login.jsp");
        dispatcher.forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String loginMessage = "";
        if (UserInfo.username.equals(username) && UserInfo.password.equals(password)) {
            HttpSession session = req.getSession(true);
            session.setAttribute("username", username);
            resp.sendRedirect(req.getContextPath() + "/home/");
            return;
        } else {
            loginMessage = "username or password is not correct";
            req.setAttribute("message", loginMessage);
            RequestDispatcher dispatcher = req.getRequestDispatcher("/static/views/login.jsp");
            dispatcher.forward(req, resp);
        }
    }
}


MainServlet.java
package com.vanthuong.tutorial.servlet;

import java.io.IOException;

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

@WebServlet(urlPatterns = { "/", "/home/" })
public class MainServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        RequestDispatcher rd = req.getRequestDispatcher("/static/views/home.jsp");
        rd.forward(req, resp);
    }
}

ProductServlet.java
package com.vanthuong.tutorial.servlet;

import java.io.IOException;

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

@WebServlet("/product/")
public class ProductServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        RequestDispatcher rd = req.getRequestDispatcher("/static/views/product.jsp");
        rd.forward(req, resp);
    }
}

Nhìn qua 3 file Servlet này, thật ra chúng ta cũng không chẳng có code gì phức tạp cả, chủ yếu là để forward URL mà thôi. Nên có lẽ là tôi cũng không cần giải thích gì về nó.

AuthFilter.java
package com.vanthuong.tutorial.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@WebFilter("/*")
public class AuthFilter implements Filter {
    private List<String> whiteURLs;

    public void init(FilterConfig filterConfig) throws ServletException {
        // Hard code for white list URLs
        whiteURLs = new ArrayList<String>();
        whiteURLs.add("/login/");
        whiteURLs.add("/home/");
        whiteURLs.add("/static/");
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        String path = request.getRequestURI().substring(request.getContextPath().length());
        if (checkWhiteURL(path)) {
            chain.doFilter(req, resp);
        } else {
            HttpSession session = request.getSession();
            String userInfo = (String) session.getAttribute("username");
            if (userInfo != null) {
                chain.doFilter(req, resp);
            } else {
                RequestDispatcher rd = request.getRequestDispatcher("/static/views/error.jsp");
                rd.forward(req, resp);
            }
        }
    }

    public void destroy() {
    }

    private boolean checkWhiteURL(String path) {
        boolean isWhiteURL = false;
        for (String whiteURL : whiteURLs) {
            isWhiteURL = "/".equals(path) || path.contains(whiteURL) ? true : false;
            if (isWhiteURL)
                break;
        }
        return isWhiteURL;
    }
}

Bây giờ mới thật sự cần chú ý đây. Đầu tiên bạn cần chú ý chính là Annotation @WebFilter, chúng ta sử dụng nó để lọc tất cả các URL được request từ phía client. Tiếp đến, trong phương thức init() tôi đã hard-code những URLs mà tôi cho phép bỏ qua không yêu cầu phải đăng nhập vì vậy nên tôi gọi chúng là những white URLs. Trong phương thức doFilter(), các request sẽ được lọc trong phương thức này nhé. Lúc này tôi sẽ lấy đường dẫn tương đối của request để bắt đầu kiểm tra xem có nằm trong white URLs không, nếu có thì tôi sẽ cho nó đi tiếp bằng cách gọi phương thức chain.doFilter(req, resp). Ngược lại không nằm trong White URLs thì tôi bắt đầu kiểm tra xem người dùng đã đăng nhâp chưa, nếu chưa thì thì tôi trả về trang lỗi luôn. Nếu đã đăng nhập rồi thì tôi sẽ chấp nhận request này và cho nó đi tiếp.

OK, bây giờ chúng ta sẽ bổ sung các view bằng JSP và deploy trên tomcat thử nhé.
views/partials/header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<%@ taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c'%>
<div style="background: #E0E0E0; height: 55px; padding: 5px;">
 <div style="float: left">
  <h1 style="margin: 0; padding: 10px">Demo Filter</h1>
 </div>
 <div style="float: right; padding: 10px; text-align: right;">
  <!-- User store in session with attribute: username -->
  Hello <b><c:choose>
    <c:when test="${username != null}">${username}</c:when>
    <c:otherwise>Guest</c:otherwise>
   </c:choose></b>
 </div>
</div>

views/partials/menu.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
   pageEncoding="UTF-8"%>
    
<div style="padding: 5px;">
   <a href="${pageContext.request.contextPath}/home/">Home</a>
   |
   <a href="${pageContext.request.contextPath}/product/">Product List</a>
   |
   <a href="${pageContext.request.contextPath}/login/">Login</a>
</div>

views/partitals/footer.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<div style="background: #E0E0E0; text-align: center; padding: 5px; margin-top: 10px;">&copy; 2016 by VanThuong.Com
</div>

views/home.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Home page</title>
</head>
<body>
 <jsp:include page="partials/header.jsp"></jsp:include>
 <jsp:include page="partials/menu.jsp"></jsp:include>
 <h3>Welcome to my site</h3>
 <jsp:include page="partials/footer.jsp"></jsp:include>
</body>
</html>

views/login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
 <jsp:include page="partials/header.jsp"></jsp:include>
 <jsp:include page="partials/menu.jsp"></jsp:include>
 <h3>Login Page</h3>
 <p style="color: red;">${message}</p>
 <form method="post" action="${pageContext.request.contextPath}/login/">
  <table>
   <tr>
    <td>User Name</td>
    <td><input type="text" name="username" value="${username}" />
    </td>
   </tr>
   <tr>
    <td>Password</td>
    <td><input type="password" name="password" /></td>
   </tr>
   <tr>
    <td colspan="2"><input type="submit" value="Submit" /> <a
     href="${pageContext.request.contextPath}/">Cancel</a></td>
   </tr>
  </table>
 </form>
 <p style="color: blue;">Correct User: admin/123456</p>
 <jsp:include page="partials/footer.jsp"></jsp:include>
</body>
</html>

views/product.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Product page</title>
</head>
<body>
 <jsp:include page="partials/header.jsp"></jsp:include>
 <jsp:include page="partials/menu.jsp"></jsp:include>

 <h3>Product Page</h3>
 <p>We are going to display products here</p>
 <jsp:include page="partials/footer.jsp"></jsp:include>
</body>
</html>

views/error.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%>  
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Error</title>
</head>
<body>
<p>You don't have permission to access the requested web page. Please <a href="<c:url value="/login/"/>">login</a> to access this page</p>
</body>
</html>

OK, bây giờ hãy deploy nó với tomcat server nhé, khi chạy lên các bạn sẽ được một trang tương tự như thế này.
Demo Filter - Homepage

Nếu như bạn bấm vào menu Product, nó sẽ hiển thị trang lỗi bởi vì bạn chưa đăng nhập.
Demo Filter - Error page


Bây giờ hãy thử đăng nhập với tài khoản admin/123456 như tôi đã hard-code sẵn nhé. Sau đó thì quay trở lại trang Product xem nội dung.
Demo Filter - Login Page

Demo Filter - Product page

Tóm lại, đây chỉ là một ví dụ đơn giản về Filter trong Servlet. Hi vọng rằng trong những bài viết tiếp theo tôi sẽ có chuẩn bị nội dung hay hơn để chia sẻ cùng bạn. Chúc các bạn học tốt nhé.