0%

JavaWeb学习(十)——过滤器

过滤器

当浏览器发送请求给服务器的时候,先执行过滤器,然后才访问Web的资源。服务器响应Response,从Web资源抵达浏览器之前,也会途径过滤器。

过滤一些敏感的字符串【规定不能出现敏感字符串】、避免中文乱码【规定Web资源都使用UTF-8编码】、权限验证【规定只有带Session或Cookie的浏览器,才能访问web资源】等等等

也就是说:当需要限制用户访问某些资源时、在处理请求时提前处理某些资源、服务器响应的内容对其进行处理再返回、我们就是用过滤器来完成的!

filter是如何实习拦截的?

Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:

  1. 调用目标资源之前,让一段代码执行。
  2. 是否调用目标资源(即是否让用户访问web资源)。
  3. 调用目标资源之后,让一段代码执行。

  web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对 象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方 法,即web资源就会被访问,否则web资源不会被访问。

api

1
2
3
4
5
public interface Filter{
void init(FilterConfig varl);
void doFilter(ServletRequest val1,ServletResponse val2,FilterChain val3);
void destroy();
}

创建:

filter的创建和销毁是由web服务器负责的。web程序启动时,web服务器将创建Filter 的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

销毁:

Web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。

filter开发和部署

Filter开发分为二个步骤:

  1. 编写java类实现Filter接口,并实现其doFilter方法。
  2. 在 web.xml 文件中使用<filter>和<filter-mapping>元素对编写的filter类进行注册,并设置它所能拦截的资源。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* @Description:filter的三种典型应用:
* 1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,
* 即是否让目标资源执行
* 2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
* 3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能
*/
public class FilterDemo01 implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("----过滤器初始化----");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {

//对request和response进行一些预处理
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

System.out.println("FilterDemo01执行前!!!");
chain.doFilter(request, response); //让目标资源执行,放行
System.out.println("FilterDemo01执行后!!!");
}

@Override
public void destroy() {
System.out.println("----过滤器销毁----");
}
}

filter的部署

注册filter

<filter>用于注册过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   <filter>
<description>FilterDemo02过滤器</description>
<filter-name>FilterDemo02</filter-name>
<filter-class>me.gacl.web.filter.FilterDemo02</filter-class>
<!--配置FilterDemo02过滤器的初始化参数-->
<init-param>
<description>配置FilterDemo02过滤器的初始化参数</description>
<param-name>name</param-name>
<param-value>gacl</param-value>
</init-param>
<init-param>
<description>配置FilterDemo02过滤器的初始化参数</description>
<param-name>like</param-name>
<param-value>java</param-value>
</init-param>
</filter>
  • <filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
  • <filter-class>元素用于指定过滤器的完整的限定类名
  • <init-param>元素用于为过滤器指定初始化参数,它的子元素、<param-name>指定参数的名字,<param-value>指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数
  • <description>用于添加描述信息,该元素的内容可为空,<description>可以不配置。

filter-mapping(映射过滤器)

<filter-mapping>元素用于设置一个Filter 所负责拦截的资源

一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径

1
2
3
4
<filter-mapping>
<filter-name>FilterDemo1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • <filter-name>子元素用于设置filter的注册名称。该值必须是在元素中声明过的过滤器的名字
  • <url-pattern>设置 filter 所拦截的请求路径(过滤器关联的URL样式)
  • <servlet-name>指定过滤器所拦截的Servlet名称
  • <dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。

dispatcher

子元素可以设置的值及其意义:

  • REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
  • INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
  • FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
  • ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

通过注解配置

1
2
    @WebFilter(filterName = "FilterDemo1",urlPatterns = "/*")
//:“/*”的含义是所有的Web资源都需要途径过滤器

Filter链

​ 在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
  web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

简单应用

javaweb学习总结(四十三)——Filter高级开发 - 孤傲苍狼 - 博客园 (cnblogs.com)
javaweb学习总结(四十六)——Filter(过滤器)常见应用 - 孤傲苍狼 - 博客园 (cnblogs.com)

filter的三种典型应用:

  • 1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
  • 2、在让目标资源执行之前,可以对requestresponse作预处理,再让目标资源执行
  • 3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能

禁止浏览器缓存所有动态页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

//让Web资源不缓存,很简单,设置http中response的请求头即可了!

//我们使用的是http协议,ServletResponse并没有能够设置请求头的方法,所以要强转成HttpServletRequest

//一般我们写Filter都会把他俩强转成Http类型的
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;

response.setDateHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");

//放行目标资源的response已经设置成不缓存的了
chain.doFilter(request, response);
}
  • 没有过滤之前,响应头是这样的:

  • 过滤之后,响应头是这样的:


实现自动登陆

开发实体、集合模拟数据库、Dao

  • 实体:
1
2
3
4
5
6
7
8
9
10
11
12
13
private String username ;
private String password;


public User() {
}

public User(String username, String password) {
this.username = username;
this.password = password;
}

//各种setter和getter
  • 集合模拟数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserDB {

private static List<User> users = new ArrayList<>();



static {
users.add(new User("aaa", "123"));
users.add(new User("bbb", "123"));
users.add(new User("ccc", "123"));
}

public static List<User> getUsers() {
return users;
}

public static void setUsers(List<User> users) {
UserDB.users = users;
}
}
  • 开发dao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    public User find(String username, String password) {

List<User> userList = UserDB.getUsers();

//遍历List集合,看看有没有对应的username和password
for (User user : userList) {
if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
return user;
}
}
return null;
}
//根据用户名可以找到用户
public User find(String username) {
List<User> userList = UserDB.getUsers();

//遍历List集合,看看有没有对应的username和password
for (User user : userList) {
if (user.getUsername().equals(username)) {
return user;
}
}

return null;
}

登陆界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form action="${pageContext.request.contextPath}/LoginServlet">

用户名<input type="text" name="username">
<br>
密码<input type="password" name="password">
<br>

<input type="radio" name="time" value="10">10分钟
<input type="radio" name="time" value="30">30分钟
<input type="radio" name="time" value="60">1小时
<br>

<input type="submit" value="登陆">

</form>

处理登陆的Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//得到客户端发送过来的数据
String username = request.getParameter("username");
String password = request.getParameter("password");

UserDao userDao = new UserDao();
User user = userDao.find(username, password);

if (user == null) {
request.setAttribute("message", "用户名或密码是错的!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}

//如果不是为空,那么在session中保存一个属性
request.getSession().setAttribute("user", user);
request.setAttribute("message", "恭喜你,已经登陆了!");

//如果想要用户关闭了浏览器,还能登陆,就必须要用到Cookie技术了
Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + user.getPassword());

//设置Cookie的最大声明周期为用户指定的
cookie.setMaxAge(Integer.parseInt(request.getParameter("time")) * 60);

//把Cookie返回给浏览器
response.addCookie(cookie);

//跳转到提示页面
request.getRequestDispatcher("/message.jsp").forward(request, response);

过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
        HttpServletResponse response = (HttpServletResponse) resp;
HttpServletRequest request = (HttpServletRequest) req;

//如果用户没有关闭浏览器,就不需要Cookie做拼接登陆了
if (request.getSession().getAttribute("user") != null) {
chain.doFilter(request, response);
return;
}

//用户关闭了浏览器,session的值就获取不到了。所以要通过Cookie来自动登陆
Cookie[] cookies = request.getCookies();
String value = null;
for (int i = 0; cookies != null && i < cookies.length; i++) {
if (cookies[i].getName().equals("autoLogin")) {
value = cookies[i].getValue();
}
}


//得到Cookie的用户名和密码
if (value != null) {

String username = value.split("\\.")[0];
String password = value.split("\\.")[1];

//在Cookie拿到的密码是md5加密过的,不能直接与数据库中的密码比较
UserDao userDao = new UserDao();
User user = userDao.find(username);

//通过用户名获得用户信息,得到用户的密码,用户的密码也md5一把

String dbPassword = md5.md5(user.getPassword());
//如果两个密码匹配了,就是正确的密码了
if (password.equals(dbPassword)) {
request.getSession().setAttribute("user", user);
}

}
;

过滤敏感词

要实现这样的功能也很简单,用户输入的敏感词肯定是在getParameter()获取的,我们在getParameter()得到这些数据的时候,判断有没有敏感词汇,如果有就替换掉就好了!简单来说:也是要增强request对象

增强request对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class MyDirtyRequest extends HttpServletRequestWrapper {

HttpServletRequest request;

//定义一堆敏感词汇
private List<String> list = Arrays.asList("傻b", "尼玛", "操蛋");

public MyDirtyRequest(HttpServletRequest request) {
super(request);
this.request = request;
}

@Override
public String getParameter(String name) {

String value = this.request.getParameter(name);

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

//遍历list集合,看看获取得到的数据有没有敏感词汇
for (String s : list) {

if (s.equals(value)) {
value = "*****";
}
}

return value ;
}
}

开发过滤器

1
2
3
4
5
6
7
8
9
10
11
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

//将request和response强转成http协议的
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;

MyDirtyRequest dirtyRequest = new MyDirtyRequest(httpServletRequest);

//传送给目标资源的是被增强后的request对象
chain.doFilter(dirtyRequest, httpServletResponse);
}