一篇搞定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技术的公众号:理想二旬不止

相关文章
相关标签/搜索