为什么需要过滤器?
先来看一个例子:
我们在登录网站页面时,需要先进行登录验证。
用户访问的正常的流程应该是:
用户先通过登录页面进行验证,然后才可以访问各种页面。
为了防止用户绕过登录验证,我们需要在每个页面进行验证, 获取session,验证用户是否登录过。
但是上述的方法又会产生下面的问题:
这时候就需要filter过滤器,它可以统一进行验证,比如权限,身份的验证,还可以进行日志记录,事务管理等...
过滤器介绍
上述整个过程如下:
需求:在web工程下,有后台管理目录manage,要求该目录下所有资源(html,图片,jsp,servlet)等资源需要用户登录后才能访问。
上述流程如下:
首先创建项目,添加web支持,添加需要的jar包
完成模块的整体流程
先完成一个正确完整的流程,再加入其它的功能,完善功能。
总地来说就是先把框架搭建起来,再完善细节。
2.1 先完整一个正确的流程-看到一个效果->后面代码就可以验证
2.2 加入其它功能
2.3 完善功能
配置Tomcat
login.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2022/11/28
Time: 17:03
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>管理后台页面登录</title>
</head>
<body>
<h1>管理后台页面登录</h1>
<form action="<%=request.getContextPath()%>/loginCheckServlet" method="post">
u: <input type="text" name="username"/><br/><br/>
p: <input type="password" name="password"/><br/><br/>
<input type="submit" value="用户登录"/>
</form>
</body>
</html>
LoginCheckServlet:
package com.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class LoginCheckServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取到用户名和密码(这里应到数据库获取)
// 这里假设密码是123456就可以通过
String username = request.getParameter("username");
String password = request.getParameter("password");
if ("123456".equals(password)) {
//合法,将用户名加入session
request.getSession().setAttribute("username", username);
//请求转发到admin.jsp
//根据过滤器原理:请求转发的链接路径是不会被过滤器拦截的,而重定向会被过滤器拦截
request.getRequestDispatcher("/manage/admin.jsp").forward(request, response);
} else {
//非法
//返回登录页面
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
}
admin.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2022/11/28
Time: 17:05
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>admin</title>
<%--指定浏览器解析的目录--%>
<base href="<%=request.getContextPath()%>/manage/">
</head>
<body>
<h1>后台管理</h1>
<a href="#">用户列表</a>||<a href="#">添加用户</a>||<a href="#">删除用户</a>
<hr>
<%--该图片放在manage目录下--%>
<img src="myphoto.png" height="300"/>
</body>
</html>
ManageFilter:
package com.filter;
import javax.servlet.*;
import java.io.IOException;
public class ManageFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//当Tomcat创建filter后,会调用该方法,进行初始化
System.out.println("ManageFilter init方法被调用...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//当每次调用该filter时,doFilter方法就会被调用
System.out.println("ManageFilter doFilter被调用...");
}
@Override
public void destroy() {
//当filter对像被销毁时,就会调用该方法
System.out.println("ManageFilter destroy被调用...");
}
}
在web.xml配置Filter和Servlet:
<?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">
<!--filter一般写在其他servlet的前面-->
<!--
1.filter的配置和servlet非常相似,filter也是被Tomcat管理和维护的
(它的创建和销毁也是tomcat来处理的)
2.<url-pattern> 配置的是filter生效的规则。即当请求的url和其匹配的时候,会触发调用该filter
3. /manage/* 里面第一个斜杠/,会解析成http://ip:port/工程路径
4. 完整的路径就是 http://ip:port/工程路径/manage/*
意为当请求的url资源满足该条件时,都会调用该filter
-->
<filter>
<filter-name>ManageFilter</filter-name>
<filter-class>com.filter.ManageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManageFilter</filter-name>
<url-pattern>/manage/*</url-pattern>
</filter-mapping>
<!--配置servlet-->
<servlet>
<servlet-name>LoginCheckServlet</servlet-name>
<servlet-class>com.servlet.LoginCheckServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginCheckServlet</servlet-name>
<url-pattern>/loginCheckServlet</url-pattern>
</servlet-mapping>
</web-app>
redeployTomcat,可以看到后台输出:
这说明filter对象是由Tomcat去创建的。我们先来看一下filter底层实现。
filter的xml配置和servlet非常相似,filter也是被Tomcat管理和维护的。我们之前在学习servlet的时候知道了Tomcat是如何管理维护servlet的:
详见:手动实现Tomcat底层机制+自己设计servlet->day03-实现02->3.4容器设计
同理,我们也可以这样理解:Tomcat还维护了两个关于filter的容器。一个容器filterURLMapping存放url-pattern和filter-name。另一个容器filterMapping存放filter-name和filter实例。
一旦Tomcat启动以后:
Tomcat接收到请求时:获取请求中的url,和filterURLMapping容器中的url进行匹配。
并且,有了filter机制后,在调用servlet前会先匹配filter。
回到之前的程序,我们在浏览器中直接访问manage目录下的资源admin.jsp,可以看到后台输出如下:
说明过滤器已经起作用了。
如果没有显示doFilter被调用,可能是因为浏览器缓存机制,建议在调试台点击禁用缓存。
现在来完善ManageFilter过滤器里面的业务代码:
既然请求manage目录下的资源首先要经过过滤器,那么我们可以在过滤器中获取session,如果发现session中已经保存了用户信息(即登录过),就放行;如果没有session信息,就返回登录页面。
package com.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class ManageFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//当Tomcat创建filter后,会调用该方法,进行初始化
System.out.println("ManageFilter init方法被调用...");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
//当每次调用该filter时,doFilter方法就会被调用
System.out.println("ManageFilter doFilter被调用...");
//如果这里没有调用继续请求的方法,就会停止在这
// 如果继续访问目标资源-->等价于放行
// 在调用过滤器前,request对象已经被创建并封装起来了
// 所以我们这里就可以通过servletRequest获取很多信息,比如访问url,session,比如访问的参数...
// 同时可以做事务管理,数据获取,日志管理等等
//获取session
//用户浏览器向服务器发送带有jsessionid的cookie,服务器根据该sid找到对应session
//这样写后面还可以接续使用httpServletRequest的相关方法
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
//获取username session对象,后面还可以继续使用
Object username = session.getAttribute("username");
//如果在对应的session中,没有找到用户的用户名,就说明没有登录过,否则就是登录过
if (username != null) {
//用户登录成功过,直接放行
/**
* filterChain.doFilter(servletRequest, servletResponse);
* 1.doFilter代表继续访问目标资源
* 2.ServletRequest和 ServletResponse对象会传递给目标资源/文件
* 3.一定要理解filter传递的两个对象,在一次http请求中,
* 和后面的servlet/jsp中的request,response是同一个对象
*/
filterChain.doFilter(servletRequest, servletResponse);
} else {//说明没有登录过,令其返回登录页面
servletRequest.getRequestDispatcher("/login.jsp")
.forward(servletRequest, servletResponse);
}
/**
* 关于chain.doFilter()方法:
* 在doFilter()方法中,在chain.doFilter()之前的代码,一般是对request执行的过滤操作;
* 在chain.doFilter()后面的代码,一般是对response执行的操作;
* chain.doFiter()执行下一个过滤器或者业务处理器。
* 如果在doFilter()方法中,不写chain.doFilter(),业务无法继续往下处理。
*/
}
@Override
public void destroy() {
//当filter对像被销毁时,就会调用该方法
System.out.println("ManageFilter destroy被调用...");
}
}
redeployTomcat,在浏览器访问login.jsp,因为login.jsp不在过滤器配置的url下,因此请求不会被拦截。
在login.jsp输入正确的用户数据,点击登录,可以看到后台成功转发到了manage目录下的admin.jsp。
整个过程为:
用户在login.jsp页面输入数据,login.jsp将表单数据提交到loginCheckServlet中检查
servlet发现数据合法,于是给浏览器返回一个jsessionid,并在服务器内存中创建和此jsessionid关联的session,然后在此session中放入用户数据,最后请求转发到/manage/admin.jsp。
根据filter原理,请求转发不会经过过滤器。但是在返回admin.jsp后,浏览器需要向服务器请求图片资源,并且,我们的图片资源刚好匹配在filter配置的url。因此关于图片的请求,将会经过过滤器。
一次请求只能返回一个资源,想要获取图片资源,因此浏览器发出了第二次请求,去获取图片资源。
综上所述,在这次过程中,只经过了一次过滤器,后台输出如下:
这时,打开浏览器新页面,访问http://localhost:8080/filter/manage/admin.jsp,可以看到访问成功,因为之前已经登录过了。
filterChain.doFilter(servletRequest, servletResponse);
chain.doFilter将请求转发给过滤器链下一个filter , 如果没有filter那就是你请求的资源
在doFilter()方法中,在chain.doFilter()之前的代码,一般是对request执行的过滤操作;在chain.doFilter()后面的代码,一般是对response执行的操作;chain.doFiter()执行下一个过滤器或者业务处理器。如果在doFilter()方法中,不写chain.doFilter(),业务无法继续往下处理;
doFilter代表继续访问目标资源,如servlet,jsp,或是下一个filter
ServletRequest和 ServletResponse对象会传递给目标资源/文件
在一次http请求中,filter传递的两个对象,和后面的servlet/jsp中的requestresponse是同一个对象
验证:filter传递的两个对象,在一次http请求中,与其后面的servlet/jsp中的request/response是同一对象
在ManageFilter中:
在admin.jsp中:
想法:我们直接访问web应用下的/manage/admin.jsp,因为该资源在filter的url规则下,因此当访问时,会调用过滤器,而过滤器中又将ServletRequest传递给admin.jsp。
我们同时在filter和admin.jsp中输出request,查看对象的hash值如何。
后台输出如下:两者的哈希值是一样的。
这说明在一个请求里面,过滤器里面的ServletRequest,Servletresponse,和后面目标资源拿到的request,response是同一个对象。是可以共用的。
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
我们将过滤器中的ServletRequest强转为HttpServletRequest类型,由于HttpServletRequest中有很多方法,所以我们可以在过滤器中做一些日志记录。
url-pattern:Filter的拦截路径,即浏览器在请求什么位置的资源时,过滤器会进行拦截过滤
精确匹配:<url-pattern>/a.jsp</url-pattern>
对应的请求地址:http://ip[域名]:port/工程路径/a.jsp会被拦截
目录匹配<url-pattern>/manage/*</url-pattern>
对应的请求地址:http://ip[域名]:port/工程路径/manage/xxx,即web工程manage目录下所有资源会被拦截
后缀名匹配<url-pattern>*.jsp</url-pattern>,后缀名可变,比如*.action,*.do,*.css等。
对应的请求地址:http://ip[域名]:port/工程路径/xx.jsp,后缀名为.jsp的请求会被拦截
Filter过滤器只关心它请求的地址是否匹配,不关心请求的资源是否存在。
是否有可能:before_filter:authenticate_user!||:authenticate_admin! 最佳答案 before_filter:do_authenticationdefdo_authenticationauthenticate_user!||authenticate_admin!end 关于ruby-on-rails-before_filter运行多个方法,我们在StackOverflow上找到一个类似的问题: https://
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s
我有一个名为Post的类,我需要能够适应以下场景:如果用户选择了一个类别,则只显示该类别的帖子如果用户选择了一种类型,则只显示该类型的帖子如果用户选择了一个类别和类型,则只显示该类别中该类型的帖子如果用户没有选择任何内容,则显示所有帖子我想知道我的Controller是否不可避免地会因大量条件语句而显得粗糙...这是我解决此问题的错误方法-有谁知道我如何才能做到这一点?classPostsController 最佳答案 您最好遵循“胖模型,瘦Controller”的惯例,这意味着您应该将这种逻辑放在模型本身中。Post类应该能够报告
我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?
我仍然收到标题中的“错误”消息,但不知道如何解决。在ApplicationController中,classApplicationController在routes.rb#match'set_activity_account/:id/:value'=>'users#account_activity',:as=>:set_activity_account--thisdoesn'tworkaswell..resources:usersdomemberdoget:action_a,:action_bendcollectiondoget'account_activity'endend和User
对于用户模型,我有一个过滤器来检查用户的预订状态,该状态由整数值(0、1或2)表示。UserActiveAdmin索引页上的过滤器是通过以下代码实现的:filter:booking_status,as::select然而,这会导致下拉选项为0、1或2。当管理员用户从下拉列表中选择它们时,我更愿意自己将它们命名为“未完成”、“待定”和“已确认”之类的名称。有没有办法在不改变booking_status在模型中的表示方式的情况下做到这一点? 最佳答案 假设booking_status是模型中的枚举字段,您可以使用:过滤器:booking
我有一个任务列表(名称、starts_at),我试图在每日View中显示它们(就像iCal)。deftodays_tasks(day)Task.find(:all,:conditions=>["starts_atbetween?and?",day.beginning,day.ending]end我不知道如何将Time.now(例如“2009-04-1210:00:00”)动态转换为一天的开始(和结束),以便进行比较。 最佳答案 deftodays_tasks(now=Time.now)Task.find(:all,:conditio
什么是0day漏洞?0day漏洞,是指已经被发现,但是还未被公开,同时官方还没有相关补丁的漏洞;通俗的讲,就是除了黑客,没人知道他的存在,其往往具有很大的突发性、破坏性、致命性。0day漏洞之所以称为0day,正是因为其补丁永远晚于攻击。所以攻击者利用0day漏洞攻击的成功率极高,往往可以达到目的并全身而退,而防守方却一无所知,只有在漏洞公布之后,才后知后觉,却为时已晚。“后知后觉、反应迟钝”就是当前安全防护面对0day攻击的真实写照!为了方便大家理解,中科三方为大家梳理当前安全防护模式下,一个漏洞从发现到解决的三个时间节点:T0:此时漏洞即0day漏洞,是已经被发现,还未被公开,官方还没有相
以下模型通过belongs_to链接:require'mongoid'classSensorincludeMongoid::Documentfield:sensor_id,type:Stringvalidates_uniqueness_of:sensor_idend...require'mongoid'require_relative'sensor.rb'classSensorDataincludeMongoid::Documentbelongs_to:sensorfield:date,type:Datefield:ozonMax1h,type:Floatfield:ozonMax8h
ruby1.9.3dev(2011-09-23修订版33323)[i686-linux]轨道3.0.20最近为什么在与DateTimeonRails相关的RSpecs项目上工作我发现在给定日期以下语句发出的值date.end_of_day.to_datetime和date.to_datetime.end_of_day虽然它们表示相同的日期时间,但比较时返回false。为了确认这一点,我打开了Rails控制台并尝试了以下操作1.9.3dev:053>monday=Time.now.monday=>2013-02-2500:00:00+05301.9.3dev:054>monday.cla