Filter:过滤器

引言

我们可以通过使用前面的技术,做出一些简单的登陆注册以及配合数据库实现对数据增删改查的Demo,程序是基本运行起来了,但是却存在着一个重大的安全问题,那就登陆权限验证,一般来说登陆的正确流程是这样的:用户在客户端发出请求 -> 后台判断是否登录 -> 是则不限制,否则 跳转回登录页面,判断是否登录和我们前面所学习的 Header中获取referer再判断达从而到防盗链的效果有相似的感觉,就是起一个判断过滤的样子,而Filter则是一个更好的解决这样问题的技术,当然强大的功能不止这一点,下面我们就好好来说一说!

(一) 过滤器概述

过滤器,顾名思义就是起到过滤筛选作用的一种事物,只不过相较于现实生活中的过滤器,这里的过滤器过滤的对象是客户端访问的web资源,也可以理解为一种预处理手段,对资源进行拦截后,将其中我们认为的杂质(用户自己定义的)过滤,符合条件的放行,不符合的则拦截下来

当然,过滤器既可以拦截request,也可以拦截返回的response,我们来看一张图

(二) 第一个过滤器程序

过滤器的本质就是一个实现了 Filter 接口的 Java 类

我们先自己创建一个类,实现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中的名字对应,从而指向到对应的文件中

<url-pattern></url-pattern>设置filter所拦截的路径 ※ 这里决定了什么样的资源会被过滤器拦截处理

拦截路径设置

格式 解释
/test.jsp 只有访问test.jsp这个资源的时候才会执行过滤器
/test/* 访问test下所有资源你的时候,执行过滤器
*.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技术的公众号:理想二旬不止

一篇搞定Java过滤器的更多相关文章

  1. 一篇搞定Java集合类原理

    Java集合类实现原理 1.Iterable接口 定义了迭代集合的迭代方法 iterator() forEach() 对1.8的Lambda表达式提供了支持 2. Collection接口 定义了集合 ...

  2. 一篇搞定RSA加密与SHA签名|与Java完全同步

    基础知识 什么是RSA?答:RSA是一种非对称加密算法,常用来对传输数据进行加密,配合上数字摘要算法,也可以进行文字签名. RSA加密中padding?答:padding即填充方式,由于RSA加密算法 ...

  3. 几周内搞定Java的10个方法

    不要将Java与JavaScript弄混了,Java的目标是“一次编译,到处调试”(呃,不对,是“到处运行”).简单来说,就是Java程序可以直接在任何设备上运行. Java语言是什么? 不管我们是否 ...

  4. 一分钟搞定Java高频面试题

    一分钟搞定Java高频面试题 一.变量赋值和计算 题目: public static void main(String[] args) { int i = 1; i = i++; int j = i+ ...

  5. 2021升级版微服务教程6—Ribbon使用+原理+整合Nacos权重+实战优化 一篇搞定

    2021升级版SpringCloud教程从入门到实战精通「H版&alibaba&链路追踪&日志&事务&锁」 教程全目录「含视频」:https://gitee.c ...

  6. 10分钟搞定 Java 并发队列好吗?好的

    | 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...

  7. 【java 数据结构】还不会二叉树?一篇搞定二叉树

    二叉树是我们常见的数据结构之一,在学习二叉树之前我们需要知道什么是树,什么是二叉树,本篇主要讲述了二叉树,以及二叉树的遍历. 你能get到的知识点? 1.树的介绍 2.二叉树的介绍 3.二叉树遍历的四 ...

  8. 【搞定 Java 并发面试】面试最常问的 Java 并发进阶常见面试题总结!

    本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.觉得内容不错 ...

  9. 一键搞定Java桌面应用安装部署 —— exe4j + Inno Setup 带着JRE, 8M起飞

    转载自:http://www.blogjava.net/huliqing/archive/2008/04/18/193907.html 对于作Java桌面应用来说,比较烦人的就是安装部署问题,客户端是 ...

随机推荐

  1. [codevs]线段树练习5

    http://codevs.cn/problem/4927/ #include <iostream> #include <cstdio> #include <algori ...

  2. 国内Archlinux arm的镜像源

    清华 http://mirrors.tuna.tsinghua.edu.cn/archlinuxarm/arch/arch/repo 中科大 http://mirrors.ustc.edu.cn/ar ...

  3. enablePullDownRefresh的使用

    1.首先要在app.json里面去将enablePullDownRefresh设置为true. 2.js 3.现象

  4. lucene正向索引(续)——一个文档的所有filed+value都在fdt文件中!!!

    4.1.3. 域(Field)的数据信息(.fdt,.fdx) 域数据文件(fdt): 真正保存存储域(stored field)信息的是fdt文件 在一个段(segment)中总共有segment ...

  5. Canvas恢复布局

    package com.loaderman.customviewdemo; import android.content.Context; import android.graphics.Canvas ...

  6. OSError: image file is truncated (28 bytes not processed)

    解决办法: 在代码中添加两行 from PIL import ImageFile ImageFile.LOAD_TRUNCATED_IMAGES = True

  7. osg 在场景中绘制坐标轴(xyz)

    //x y z font_size osg::Geode* makeCoordinate(float a_x,float a_y,float a_z,float font_size) { osg::r ...

  8. 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_07-SpringSecurityOauth2研究-Oauth2授权码模式-资源服务授权测试

    下面要完成  5.6两个步骤 3.3.4 资源服务授权 3.3.4.1 资源服务授权流程 资源服务拥有要访问的受保护资源,客户端携带令牌访问资源服务,如果令牌合法则可成功访问资源服务中的资 源,如下图 ...

  9. Hive之insert into与insert overwrite区别

    一.实践先行,直接上手 1. hive 表及数据准备 建表,并插入初始数据.向表中插入 hive> use test; hive> create table kwang_test (id ...

  10. navigationBarTitleText

    想修改整个程序的导航栏,在app.json 文件 修改 "window": { "backgroundTextStyle": "light" ...