一篇搞定Java過濾器

Filter:過濾器

引言

咱們能夠經過使用前面的技術,作出一些簡單的登錄註冊以及配合數據庫實現對數據增刪改查的Demo,程序是基本運行起來了,可是卻存在着一個重大的安全問題,那就登錄權限驗證,通常來講登錄的正確流程是這樣的:用戶在客戶端發出請求 -> 後臺判斷是否登陸 -> 是則不限制,不然 跳轉回登陸頁面,判斷是否登陸和咱們前面所學習的 Header中獲取referer再判斷達從而到防盜鏈的效果有類似的感受,就是起一個判斷過濾的樣子,而Filter則是一個更好的解決這樣問題的技術,固然強大的功能不止這一點,下面咱們就好好來講一說!

(一) 過濾器概述

過濾器,顧名思義就是起到過濾篩選做用的一種事物,只不過相較於現實生活中的過濾器,這裏的過濾器過濾的對象是客戶端訪問的web資源,也能夠理解爲一種預處理手段,對資源進行攔截後,將其中咱們認爲的雜質(用戶本身定義的)過濾,符合條件的放行,不符合的則攔截下來html

固然,過濾器既能夠攔截request,也能夠攔截返回的response,咱們來看一張圖java

(二) 第一個過濾器程序

過濾器的本質就是一個實現了 Filter 接口的 Java 類web

咱們先本身建立一個類,實現Filter接口(javax.servlet),重寫其中的全部方法數據庫

@WebFilter("/*")
public class FilterDemo1 implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //放行代碼
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {
    }

}

咱們先不探究其中的方法,咱們先看一下如何配置filter瀏覽器

(三) filter配置

第一種:web.xml配置

<filter>
    <filter-name>filterDemo1</filter-name>
    <filter-class>package cn.ideal.web.filter.FilterDemo1</filter-class>
</filter>

<filter-mapping>
    <filter-name>filterDemo1</filter-name>
    <!-- 攔截路徑 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

filter

<filter-name></filter-name> :指定filter名字安全

<filter-class></filter-class> :指定filter全類名(帶包名)服務器

filter-mapping

<filter-name></filter-name> :這裏的標籤是爲了與上面filter中的名字對應,從而指向到對應的文件中app

<url-pattern></url-pattern>設置filter所攔截的路徑 ※ 這裏決定了什麼樣的資源會被過濾器攔截處理dom

攔截路徑設置

格式 解釋
/test.jsp 只有訪問test.jsp這個資源的時候纔會執行過濾器
/test/* 訪問test下全部資源你的時候,執行過濾器
*.jsp 全部jsp格式的資源被訪問的時候,執行過濾器
/* 任意資源被訪問,均執行過濾器

因爲過濾器內設置的是比較通用的一些設置,因此通常來講使用 /* 這種格式,不過也能夠根據需求狀況選擇jsp

攔截方式配置:dispatcher

攔截方式配置也就是資源被訪問的形式,有這麼幾個屬性

  • REQUEST:默認值,瀏覽器直接請求資源
  • FORWARD:轉發訪問資源 : RequestDispatcher.forward();
  • INCLUDE:包含訪問資源 : RequestDispatcher.include();
  • ERROR:錯誤跳轉資源 : 被聲明式異常處理機制調用的時候

補充:聲明式異常處理即:在web.xml中經過配置來肯定不一樣的異常類型將如何被處理,最後跳轉到哪一個頁面,也就是咱們經常看到的一些404錯誤頁面

<error-page>
      <!--異常的類-->
         <exception-type>xxx</exception-type>
      <!--異常發生時跳轉的頁面-->
        <location>xxx</location>
</error-page>

第二種:使用註解配置

與servlet類似的配置 ,咱們能夠指定它的名字和攔截路徑

@WebFilter("filterName="FilterDemo1",urlPatters="/*")

可是直接在類上聲明註解,顯然那咱們是不須要指定其名字的,而經過查看源碼又能夠知道,urlPatters又能夠被value指定,而value又能夠省略,因此咱們能夠簡寫爲

@WebFilter("/*")

若想在filter註解中配置dispatcher,咱們須要設置dispatcherTypes屬性

@WebFilter(value = "/*",dispatcherTypes ={DispatcherType.FORWARD,DispatcherType.FORWARD} )

(四) 過濾器的生命週期

講完了配置,下面咱們就回歸主題來講一說過濾器的生命週期,也就是上面實現接口而重寫的那些方法們

首先是 init(FilterConfig config) 方法和 void destroy() 方法,Servlet也有這兩個方法,二者分別在服務器啓動和關閉的時候被建立以及銷燬,二者均執行一次,用於加載以及釋放資源

其實就這兩個方法來講在Servlet的基礎上仍是很好理解的

再者就是咱們過濾器的核心方法了:

void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)

doFilter方法就是咱們真正進行攔截的方法,經過前兩個參數咱們能夠知道,不管是Request亦或是Respone咱們均可以對其進行過濾操做,那麼第三個參數是什麼意思呢?

咱們打開FilterChain的源碼

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

嗯!FilterChain是一個接口,接口內也定義了一個doFilter方法,它存在的意義是什呢?

這是一種鏈式結構,咱們在這裏稱做過濾器鏈,其做用就是爲了配置多個過濾器,多個過濾器下的執行流程是這樣的

那麼,多個過濾器誰前誰後呢?這還與咱們前面的配置有關

  • 註解配置:按照類名字符串比較,值小的先執行

    • Eg:AFilterDemo 優先於 BFilterDemo
  • web.xml配置:<filter-mapping>中誰在上面,誰優先執行

過濾器的簡單執行流程

  • 執行過濾器
  • 執行放行後的資源,多是下一個過濾器,也多是web資源(JSP/Servlet)
  • 執行過濾器放行代碼 chain.doFilter(req, resp);下邊的代碼

(五) Filter的應用

(1) 登陸權限驗證

咱們前面的的知識已經能簡單的知足咱們對於登陸以及簡單註冊的實現,可是若是咱們知道地址,直接經過url訪問一些 資源,很顯然這是很不合理的,因此咱們須要對登陸狀態進行驗證,未登陸則轉發到的登陸界面,登陸則能夠依據登陸狀態自由訪問一些頁面

咱們寫一個簡單的模擬程序,爲了可讀性,以及篇幅問題,咱們省略數據庫鏈接的部分,採用固定的密碼

這是index.jsp頁面,也就是須要登陸後才能放開訪問權限的頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<h1>這是首頁,只有登陸後才能查看</h1>
</body>
</html>

這是login.jsp頁面,也就是登陸頁面,很是簡單

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/web-test/loginServlet" method="post">
    <table>
        <tr>
            <td>用戶名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密碼:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td><input type="submit" value="登陸"></td>
        </tr>
    </table>
</form>
</body>
</html>

咱們創一個domain 包,寫一個User實體,補充其get、set方法

package cn.ideal.domain;

public class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

下面開始編寫LoginServlet,也就是處理登陸驗證問題的代碼

package cn.ideal.web.servlet;

import cn.ideal.dao.UserDao;
import cn.ideal.domain.User;

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 java.io.IOException;

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //設置編碼
        request.setCharacterEncoding("utf-8");

        //獲取請求參數
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        //封裝user對象
        User loginUser = new User();
        loginUser.setUsername(username);
        loginUser.setPassword(password);

        UserDao dao = new UserDao();
        User user = dao.login(loginUser);

        if (user == null){
            //登錄失敗
            request.getRequestDispatcher("/failServlet").forward(request,response);
        }else{
            //登陸成功
            request.getSession().setAttribute("user",user);
            request.getRequestDispatcher("/index.jsp").forward(request,response);
        }

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request,response);
    }
}

咱們根據 user 是否等於 null來判斷用戶名密碼是否正確,那麼咱們你就來寫一下這個返回了一個User對象的login方法

咱們在dao層中建立一個UserDao類,正式一些的項目會寫成接口的形式,在impl層中再寫實現,爲了掩飾咱們簡化這一步

package cn.ideal.dao;

import cn.ideal.domain.User;

public class UserDao {
    public User login(User loginUser) {
        //定義真實用戶名密碼(代替數據庫讀取)
        String trueUsername = "admin";
        String truePassword = "admin";

        if (loginUser.getUsername().equals(trueUsername) && loginUser.getPassword().equals(truePassword)) {
            //登錄成功
            return loginUser;
        } else {
            return null;
        }
    }
}

關鍵來了,這也就是咱們所講的過濾器方法,這裏所須要注意的就是 登錄成功後,記得寫入狀態

request.getSession().setAttribute("user",user);

package cn.ideal.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
public class LoginFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //獲取資源請求路徑
        String requestURI = request.getRequestURI();

        //排除包含登陸確實所須要的資源,給予放行
        if (requestURI.contains("/login.jsp") || requestURI.contains("/loginServlet")) {
            chain.doFilter(request,response);
        }else{
            //不包含,即驗證用戶是否已經登陸
            Object user = request.getSession().getAttribute("user");
            if (user != null){
                //登錄了,放行
                chain.doFilter(request,response);
            }else{
                //沒有登陸,跳轉回登陸頁面
                request.getRequestDispatcher("/login.jsp").forward(request,response);
            }
        }
    }

    public void init(FilterConfig config) throws ServletException {
    }
}

(2) 敏感詞過濾

若是咱們想要對用戶提交的一些信息進行過濾,在servlet中進行一些代碼的編寫也算一種方法,可是最爲合適的仍是fiter,它更加通用,下面咱們使用代理模式加強request從而使用filter進行敏感詞的過濾

咱們就在剛纔的index頁面上加以修改

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<h1>這是首頁,只有登陸後才能查看</h1>
<form action="/web-test/replaceServlet" method="post">
    <table>
        <tr>
            <td><input type="text" name="words"></td>
        </tr>
        <tr>
            <td><input type="submit" value="敏感字檢測"></td>
        </tr>
    </table>
</form>
</body>
</html>

咱們把傳入的參數讀取進來

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        String words = request.getParameter("words");
        System.out.println(words);
    }
package cn.ideal.web.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

@WebFilter("/*")
public class ReplaceFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //建立代理對象,加強getParameter
        ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //判斷是否是getParameter方法
                if (method.getName().equals("getParameter")){
                    //獲取返回值
                    String value = (String)method.invoke(req, args);
                    if (value != null){
                        for (String s : list){
                            if (value.contains(s)){
                                value = value.replaceAll(s,"***");
                            }
                        }
                    }
                    return value;
                }
                return method.invoke(req,args);
            }
        });
        chain.doFilter(proxy_req, resp);
    }

    private List<String> list = new ArrayList<String>();

    public void init(FilterConfig config) throws ServletException {

        try {
            //獲取文件真實路徑
            ServletContext servletContext = config.getServletContext();
            String realPath = servletContext.getRealPath("/WEB-INF/classes/replace.txt");
            //讀取文件
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(realPath),"UTF-8"));
            //將每一行數據添加到list中
            String line = null;
            while((line = br.readLine())!=null){
                list.add(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

結尾:

若是內容中有什麼不足,或者錯誤的地方,歡迎你們給我留言提出意見, 蟹蟹你們 !^_^

若是能幫到你的話,那就來關注我吧!(系列文章均會在公衆號第一時間更新)

在這裏的咱們素不相識,卻都在爲了本身的夢而努力 ❤

一個堅持推送原創Java技術的公衆號:理想二旬不止

相關文章
相關標籤/搜索