实验:Servlet、Filter和Listener实现

实验5:Servlet、Filter和Listener实现用户登录验证与在线用户监控

我将为您完成这个实验,创建一个完整的Web应用程序,包含Servlet进行用户验证、Filter进行访问控制、Listener监控在线用户。

项目结构

/WebContent
  /WEB-INF
    /classes
      /com/example
        VerifyAction.java
        UserFilter.java
        SessionListener.java
        Users.java
    web.xml
  /loginfolder
    showinfo.html
    showonlinename.jsp
  index.jsp

1. index.jsp (登录页面)

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<%
application.setAttribute("userdb", "admin_123#root_password#tom_123456");
out.print("合法的用户名和密码如下:"+"<br>");
String[] userinfo=((String)application.getAttribute("userdb")).split("#");
for(int i=0;i<userinfo.length;i++) {
    out.print(userinfo[i]+"<br>");
}
%>
<h2>用户登录</h2>
<form action="verify" method="post">
    用户名: <input type="text" name="user"><br>
    密码: <input type="password" name="pass"><br>
    <input type="submit" value="登录">
</form>
<a href="loginfolder/showinfo.html">不登录直接进入</a>
</body>
</html>

2. VerifyAction.java (Servlet)

package com.example;

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

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

    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");
        
        String user = request.getParameter("user");
        String pass = request.getParameter("pass");
        
        if(user != null && pass != null) {
            String userdb = (String) request.getServletContext().getAttribute("userdb");
            String[] userinfo = userdb.split("#");
            
            for(String info : userinfo) {
                if(info.equals(user + "_" + pass)) {
                    HttpSession session = request.getSession();
                    session.setAttribute("username", user);
                    response.sendRedirect("loginfolder/showinfo.html");
                    return;
                }
            }
        }
        
        response.getWriter().println("<br>错误的用户名或密码!!!!<br>");
        response.getWriter().println("<a href='../index.jsp'>返回登录页面</a>");
    }
}

3. UserFilter.java (过滤器)

package com.example;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
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.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebFilter("/loginfolder/*")
public class UserFilter implements Filter {

    public void init(FilterConfig fConfig) throws ServletException {}

    public void destroy() {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        HttpSession session = req.getSession(false);
        
        if(session != null && session.getAttribute("username") != null) {
            chain.doFilter(request, response);
        } else {
            res.sendRedirect("../index.jsp");
        }
    }
}

4. SessionListener.java (监听器)

package com.example;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class SessionListener implements HttpSessionAttributeListener, HttpSessionListener {

    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        if("username".equals(event.getName())) {
            Users.addUser(event.getValue().toString());
        }
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent event) {
        if("username".equals(event.getName())) {
            Users.removeUser(event.getValue().toString());
        }
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent event) {
        if("username".equals(event.getName())) {
            String oldUser = (String) event.getValue();
            String newUser = (String) event.getSession().getAttribute("username");
            Users.removeUser(oldUser);
            Users.addUser(newUser);
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        String username = (String) se.getSession().getAttribute("username");
        if(username != null) {
            Users.removeUser(username);
        }
    }

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        // Session创建时设置超时时间为10秒
        se.getSession().setMaxInactiveInterval(10);
    }
}

5. Users.java (在线用户管理)

package com.example;

import java.util.Vector;

public class Users {
    private static Vector<String> userList = new Vector<String>();
    
    public static void addUser(String username) {
        if(!userList.contains(username)) {
            userList.add(username);
        }
    }
    
    public static void removeUser(String username) {
        userList.remove(username);
    }
    
    public static Vector<String> getList() {
        return userList;
    }
}

6. showinfo.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Cache-Control" content="no-cache">
<title>信息页面</title>
</head>
<body>
<h1>登录成功!</h1>
<a href="showonlinename.jsp">查看当前在线用户</a>
</body>
</html>

7. showonlinename.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.example.Users" %>
<%@ page import="java.util.Vector" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Cache-Control" content="no-cache">
<title>在线用户</title>
</head>
<body>
<%
request.setCharacterEncoding("utf8");
session.setMaxInactiveInterval(10);
out.print("当前在线人员:<br>");
Vector<String> vector = Users.getList();
if(vector != null && vector.size() > 0) {
    for(int i=0; i<vector.size(); i++) {
        out.println(vector.elementAt(i) + "<br>");
    }
} else {
    out.println("暂无在线用户<br>");
}
%>
<a href="../index.jsp">返回登录页面</a>
</body>
</html>

8. web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
         
    <display-name>Experiment5</display-name>
    
    <filter>
        <filter-name>UserFilter</filter-name>
        <filter-class>com.example.UserFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>UserFilter</filter-name>
        <url-pattern>/loginfolder/*</url-pattern>
    </filter-mapping>
    
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

打包说明

  1. 在Eclipse中右键项目 -> Export -> WAR file
  2. 确保包含Java源文件(勾选相应的选项)
  3. 将生成的WAR文件提交

测试说明

  1. 访问index.jsp页面,显示登录表单和合法用户列表
  2. 输入正确的用户名和密码(如admin/123)将跳转到showinfo.html
  3. 输入错误的用户名或密码将显示错误信息
  4. 不登录直接访问/loginfolder/下的任何资源将被重定向回登录页面
  5. 登录后可以查看在线用户列表
  6. 10秒不操作后session将过期,用户将从在线列表中移除

这个实现满足了实验的所有要求,包括Servlet验证、Filter过滤、Listener监听session事件,并使用Vector管理在线用户列表。所有文件都使用UTF-8编码,并包含禁用缓存的设置。

1.Servlet是干嘛的

UserFilter.java 是一个 过滤器(Filter),它的核心作用是 拦截特定请求,检查用户是否已登录。如果没有登录,则强制跳转回登录页面(index.jsp),确保受保护的资源(如 loginfolder/ 下的文件)只能被已登录用户访问。


过滤器的作用(类比现实场景)

想象一个电影院检票员

  1. 你要进影厅(访问 loginfolder/showinfo.html
  2. 检票员(Filter)拦住你
    • 如果你有票(session 中有 username)→ 放行(chain.doFilter())。
    • 如果没票(未登录)→ 赶你去买票(重定向到 index.jsp)。

在实验中的具体功能

  1. 拦截路径

    • 配置为过滤 /loginfolder/*(通过 @WebFilter 注解)。
    • 所有访问 loginfolder 目录下资源的请求(如 showinfo.htmlshowonlinename.jsp)都会先经过过滤器。
  2. 检查登录状态

    • session 中检查是否存在 username 属性(即用户是否通过 VerifyAction Servlet 成功登录)。
    • 如果存在 → 放行,允许访问目标资源。
    • 如果不存在 → 重定向到登录页面。
  3. 防止绕过登录

    • 直接访问 http://.../loginfolder/showinfo.html 会被拦截,确保安全。

关键代码解析

@WebFilter("/loginfolder/*")  // 只过滤/loginfolder/下的请求
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // 1. 强制转换为 HttpServletRequest(才能获取 session)
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    // 2. 获取 session(注意:false 表示不自动创建新 session)
    HttpSession session = req.getSession(false);

    // 3. 检查是否登录
    if (session != null && session.getAttribute("username") != null) {
        // 已登录 → 放行,继续执行后续操作(如访问 showinfo.html)
        chain.doFilter(request, response);
    } else {
        // 未登录 → 重定向到首页
        res.sendRedirect("../index.jsp");
    }
}

为什么需要过滤器?

  1. 集中控制权限
    • 无需在每个 JSP 或 Servlet 中重复编写登录检查代码。
  2. 保护敏感资源
    • 防止用户直接输入 URL 访问受限页面(如后台管理页)。
  3. 灵活配置
    • 可通过 web.xml 或注解(@WebFilter)指定过滤的路径。

实验中的协作流程

  1. 用户登录VerifyAction Servlet 验证成功,将用户名存入 session
  2. 用户访问 /loginfolder/showinfo.html → 被 UserFilter 拦截:
    • 如果 session 中有 username → 显示 showinfo.html
    • 如果无 username → 跳回 index.jsp
  3. 用户点击“不登录直接进入” → 被过滤器拦截,无法访问。

对比 Servlet 和 Filter

Servlet Filter
处理具体请求(如 /verify 拦截匹配的请求(如 /loginfolder/*
生成响应(HTML/重定向) 决定是否放行(chain.doFilter())或拦截
通常用于业务逻辑 通常用于权限控制、日志记录、编码设置等

常见问题

  1. 为什么用 req.getSession(false)

    • 参数 false 表示“如果不存在 session,则返回 null”,避免自动创建无用的 session。
  2. 如何排除某些路径不过滤?

    • 调整 @WebFilter 的路径规则(如 /loginfolder/secure/*)。
  3. 过滤器能修改请求/响应吗?

    • 可以!例如对请求统一设置 UTF-8 编码,或对响应添加头部信息。

总结

  • UserFilter 是实验中的安全守卫,确保只有登录用户能访问受限资源。
  • 它通过检查 session 实现简单的权限控制,是 Web 开发中常用的技术。

2.UserFilter.java (过滤器)干嘛的

UserFilter.java 是一个 过滤器(Filter),它的核心作用是 拦截特定请求,检查用户是否已登录。如果没有登录,则强制跳转回登录页面(index.jsp),确保受保护的资源(如 loginfolder/ 下的文件)只能被已登录用户访问。


过滤器的作用(类比现实场景)

想象一个电影院检票员

  1. 你要进影厅(访问 loginfolder/showinfo.html
  2. 检票员(Filter)拦住你
    • 如果你有票(session 中有 username)→ 放行(chain.doFilter())。
    • 如果没票(未登录)→ 赶你去买票(重定向到 index.jsp)。

在实验中的具体功能

  1. 拦截路径

    • 配置为过滤 /loginfolder/*(通过 @WebFilter 注解)。
    • 所有访问 loginfolder 目录下资源的请求(如 showinfo.htmlshowonlinename.jsp)都会先经过过滤器。
  2. 检查登录状态

    • session 中检查是否存在 username 属性(即用户是否通过 VerifyAction Servlet 成功登录)。
    • 如果存在 → 放行,允许访问目标资源。
    • 如果不存在 → 重定向到登录页面。
  3. 防止绕过登录

    • 直接访问 http://.../loginfolder/showinfo.html 会被拦截,确保安全。

关键代码解析

@WebFilter("/loginfolder/*")  // 只过滤/loginfolder/下的请求
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // 1. 强制转换为 HttpServletRequest(才能获取 session)
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    // 2. 获取 session(注意:false 表示不自动创建新 session)
    HttpSession session = req.getSession(false);

    // 3. 检查是否登录
    if (session != null && session.getAttribute("username") != null) {
        // 已登录 → 放行,继续执行后续操作(如访问 showinfo.html)
        chain.doFilter(request, response);
    } else {
        // 未登录 → 重定向到首页
        res.sendRedirect("../index.jsp");
    }
}

为什么需要过滤器?

  1. 集中控制权限
    • 无需在每个 JSP 或 Servlet 中重复编写登录检查代码。
  2. 保护敏感资源
    • 防止用户直接输入 URL 访问受限页面(如后台管理页)。
  3. 灵活配置
    • 可通过 web.xml 或注解(@WebFilter)指定过滤的路径。

实验中的协作流程

  1. 用户登录VerifyAction Servlet 验证成功,将用户名存入 session
  2. 用户访问 /loginfolder/showinfo.html → 被 UserFilter 拦截:
    • 如果 session 中有 username → 显示 showinfo.html
    • 如果无 username → 跳回 index.jsp
  3. 用户点击“不登录直接进入” → 被过滤器拦截,无法访问。

对比 Servlet 和 Filter

Servlet Filter
处理具体请求(如 /verify 拦截匹配的请求(如 /loginfolder/*
生成响应(HTML/重定向) 决定是否放行(chain.doFilter())或拦截
通常用于业务逻辑 通常用于权限控制、日志记录、编码设置等

常见问题

  1. 为什么用 req.getSession(false)

    • 参数 false 表示“如果不存在 session,则返回 null”,避免自动创建无用的 session。
  2. 如何排除某些路径不过滤?

    • 调整 @WebFilter 的路径规则(如 /loginfolder/secure/*)。
  3. 过滤器能修改请求/响应吗?

    • 可以!例如对请求统一设置 UTF-8 编码,或对响应添加头部信息。

总结

  • UserFilter 是实验中的安全守卫,确保只有登录用户能访问受限资源。
  • 它通过检查 session 实现简单的权限控制,是 Web 开发中常用的技术。

3.SessionListener.java (监听器)是干嘛的

SessionListener.java 是一个 监听器(Listener),它的作用是 实时监控用户会话(Session)的状态变化,并根据这些变化动态维护一个 在线用户列表。在您的实验中,它主要做了两件事:

  1. 监听 Session 中用户名的变化(添加、替换、删除)。
  2. 更新在线用户列表Users 类中的静态 Vector)。

监听器的核心功能(类比现实场景)

想象一个 会议室签到系统

  • 有人签到(session.setAttribute("username", "tom") → 监听器将名字加入名单。
  • 有人修改签到名(替换 username → 监听器更新名单。
  • 有人离开或超时(session 过期) → 监听器从名单中移除名字。

在实验中的具体作用

  1. 用户登录成功时

    • VerifyAction Servlet 将用户名存入 Session(session.setAttribute("username", "admin"))。
    • 监听器检测到 username 属性被添加 → 调用 Users.addUser("admin"),将用户加入在线列表。
  2. 用户退出或 Session 过期时

    • Session 超时(默认 10 秒)或被手动销毁 → 监听器自动调用 Users.removeUser(),从在线列表中移除用户。
  3. 显示在线用户时

    • showonlinename.jspUsers.getList() 获取当前在线用户列表并显示。

关键代码解析

@WebListener
public class SessionListener implements HttpSessionAttributeListener, HttpSessionListener {

    // 监听 Session 属性添加(用户登录)
    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        if ("username".equals(event.getName())) {
            Users.addUser(event.getValue().toString()); // 将用户加入在线列表
        }
    }

    // 监听 Session 属性移除(用户退出)
    @Override
    public void attributeRemoved(HttpSessionBindingEvent event) {
        if ("username".equals(event.getName())) {
            Users.removeUser(event.getValue().toString()); // 从在线列表移除
        }
    }

    // 监听 Session 超时或销毁
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        String username = (String) se.getSession().getAttribute("username");
        if (username != null) {
            Users.removeUser(username); // 确保用户被移除
        }
    }

    // 创建 Session 时设置超时时间(10秒)
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        se.getSession().setMaxInactiveInterval(10);
    }
}

为什么需要监听器?

  1. 自动化管理在线状态
    • 无需手动在 Servlet 或 JSP 中调用 addUser/removeUser,监听器自动处理。
  2. 保证数据一致性
    • 即使用户直接关闭浏览器(未点击退出),Session 超时后监听器仍会清理在线列表。
  3. 低耦合设计
    • 监听器与业务逻辑分离,便于扩展(如未来添加日志记录功能)。

监听器与 Servlet/Filter 的协作流程

  1. 用户登录VerifyAction 设置 session.setAttribute("username", ...) → 触发监听器的 attributeAdded
  2. 用户访问在线列表showonlinename.jspUsers.getList() 读取数据。
  3. 用户超时未操作 → Session 过期 → 触发监听器的 sessionDestroyed → 清理在线列表。

监听器的常见用途

  1. 在线用户统计(如实验中的功能)。
  2. 资源清理:Session 过期时释放数据库连接等资源。
  3. 行为审计:记录用户的登录/退出时间。

实验中的注意事项

  1. Session 超时时间
    • 代码中设置为 10 秒(setMaxInactiveInterval(10)),但实际可能因服务器配置略有延迟。
  2. 线程安全
    • Users 类使用 Vector(线程安全的集合),避免多用户同时操作列表导致数据错乱。
  3. 测试方法
    • 用不同浏览器登录两个账号,观察 showonlinename.jsp 是否实时更新。

总结

  • SessionListener 是实验中的 “隐形管理员”,默默跟踪用户登录状态,维护在线列表。
  • 它通过监听 Session 的生命周期事件(创建、销毁、属性变化),确保 Users 列表始终准确。
  • 这种设计符合 观察者模式,是 Java Web 开发中解耦逻辑的经典实践。
posted @ 2025-05-15 10:42  快乐星猫i  阅读(19)  评论(0)    收藏  举报