1.Java Web开发实战—Listener详解—Listener简介、源码Listener开发、源码Listener的源码API、Listener应用
2.Listener(倾听者)
3.Servlet源码和Tomcat源码解析
4.tomcat反序列化漏洞(cve-2016-8735)
5.org.springframework.beans.factory.BeanDefinitionStoreException
6.Web中间件漏洞之Tomcat篇
Java Web开发实战—Listener详解—Listener简介、源码Listener开发、源码Listener的源码极客论坛源码API、Listener应用
深入探索Java Web开发实战:Listener的源码奥秘 在Java Web开发的世界里,Listener扮演着至关重要的源码角色,它如同后台的源码观察者,默默地监听并响应Web应用程序中的源码各种关键事件。本篇文章将带你走进Listener的源码世界,从基础概念、源码开发实践到API应用,源码一一详解。源码Listener基础与机制
Listener的源码核心在于其监听机制,它通过8种接口监听Web应用中的ServletContext、HttpSession和ServletRequest事件。在Eclipse中,只需选择对应的接口,如默认的javax.servlet.ServletContextListener,即可创建Listener,如TestListener,它将自动配置到web.xml中,确保监听器的执行顺序。创建Listener实战
在Eclipse中,选择Listener接口,如TestListener,勾选后自动生成相应的类,Eclipse会自动在web.xml中添加和元素,支持多Listener配置。
Listener接口详解
Java EE提供了一系列强大的Listener接口,如ServletContextListener关注ServletContext的生命周期,而ServletContextAttributeListener则关注属性的增删改。重点掌握这两大接口,如contextInitialized和contextDestroyed,分别在ServletContext创建和销毁时触发。示例代码展示
contextInitialized(ServletContextEvent): 当ServletContext创建时,执行TestListener的初始化逻辑,控制台输出"ServletContext对象被创建了"。
contextDestroyed(ServletContextEvent): ServletContext销毁时,执行销毁逻辑,控制台输出"ServletContext对象被销毁了"。
后续的Listener接口如HttpSessionListener和ServletRequestListener同样具有类似的生命周期方法,如sessionCreated(HttpSessionEvent)和requestInitialized(ServletRequestEvent),它们各自负责监听和操作相应的生命周期事件。Listener应用实战
例如,我们可以创建一个名为TestListener的类,继承HttpSessionBindingListener,刮刮彩源码用于存储用户信息。结合UserInfo单例模式,实现在线用户的管理。在Servlet中,监听用户登录和登出事件,实时更新显示信息。小结与提升
通过理解Listener的原理和使用方法,你能灵活地编写程序实现Web应用的特殊功能,如用户登录状态管理、会话统计等。在Context的jsp目录中,实践这些Listener的应用,例如,jsp.jsp通过JSTL展示用户信息,Servlet的删除操作则触发TestListener中的清理逻辑。重启Tomcat,一步步见证Listener的力量。 总而言之,掌握Listener是Java Web开发不可或缺的一部分,它能让你的应用更为智能,更加灵活。深入理解Listener的接口、机制和应用,将为你的Web开发之路增添无限可能。Listener(倾听者)
终于轮到讲讲Filter、Listener,写完这两篇,关于JavaWeb我也就无话可说了。
转行开发一年多了,很心疼去年浪费了这么多时间在SSM上。有一段时间,觉得自己好像什么都懂,但又什么都不懂。这种感觉是非常难受的。仿佛浑身充满了力气,却每一拳都打在棉花上。我想更深入地了解Java,写出更好更优雅的代码,结果买了书翻开的那一瞬间就发现不是自己想要的,书中所讲并没有直指我心里的疑问。
我的疑问是什么呢?作为非科班,自知起步比别人已经晚了一大截。虽然希望最好能搞懂整个框架,却也知道欲速则不达。然而,哪怕窥得其中一二想必也会很满足。我以为这件事很简单,老人管理系统源码但却浪费了我大半年时间。
去年年底一次偶然的机会,促使我开始了复习JavaWeb基础的漫漫长路。从此一发不可收拾,从动态代理、注解、ThreadLocal以及JDBC,一路过关斩将,收获颇丰。才发现,原来学习框架时感到无力,是特么基础不牢靠...真的是基础不牢,地动山摇,这话一点没错。
如果当初学习JavaWeb时,我对反射、动态代理、注解、泛型、JDBC等知识点有现在这程度,学会使用SSM估计就是一星期的事情...基础实在太重要了(话虽这么说,JavaWeb对于零基础来说难度是客观存在的)。
真的恨不得坐时光机回到去年抽自己一巴掌:滚回去好好学JavaWeb!望大家引以为戒!
这篇先写Listener。看完这篇,你们就会发现,关于Listener,我讲解的切入点有多骚。
主要内容:
Listener复习
大致来说,常用的监听器就是“6+2”:
感知监听都是Session相关的,我已经在 Cookie与Session讲过,这里就不提了。
6个常规监听器,分属三类,分别对应JavaWeb三大域对象(除去JSP的Page域):ServletContext、HttpSession、ServletRequest。共三对,每一对都包括1个生命周期监听和1个属性监听。
所谓生命周期监听器,就是监听三大域对象的创建和销毁。每当Tomcat创建或销毁三大域对象,都会被这些监听器察觉,然后它们会做相应的操作(调用自身的特定方法)。
属性监听器则专门监听三大域对象get/setAttribute()。每当我们给域对象设置值或者从里面取值,大型源码阅读工具都会被它们监听到,然后还是触发它们特定的方法。
先复习一下Servlet如何处理请求:
然后我们来看看监听器到底在什么时候做了什么事。
为了帮助大家理解接下来这张图的细节,我问几个问题,大家带着问题去看:
答案:
问题一
问题二
只有当在Servlet中调用request.getSession(),且根据JSESSIONID找不到对于的Session时,才会创建新的Session对象,才会被监听到。第二次请求,浏览器会带上JSESSIONID,此时虽然还是request.getSession(),但是会返回上次那个。根据JSESSIONID去Session这个过程是隐式的,我们看到的就是getSession()。
问题三
对于图中的步骤7//,也就是get/setAttribute()时,会触发属性监听。
搞清楚每一种监听器的作用以及触发时机是最重要的,使用其实很简单。去看崔老师视频吧,讲得很清楚了。
观察者模式
大家觉得Listener难在哪?
是监听器种类太多记不清吗?不会吧,不就是“6+2”吗。
是各个监听器的触发时机扑朔迷离吗?也不会啊,上面的图讲得很清楚了。
其实!大家觉得监听器难,是因为根本不知道为啥它能起作用。监听器的底层原理其实涉及到一种设计模式:观察者模式(Observer)。
先来看监听器的定义:
监听器就是一个实现了特定接口的普通Java程序,这个程序专门用于监听另一个Java对象的方法调用或者属性改变。当被监听对象发生上述事件后,监听器某个方法将立即被执行。
道理都懂,但是还是没讲到点子上。为啥监听器能监听?
程序毕竟不是人,不像警察盯着小偷,看到他偷东西就动手抓人。在我看来,一个方法能执行,必然是被调用!有两种可能:
监听器肯定不是通过定时任务实现的,毕竟它的方法调用时机是在“被监听对象特定行为发生时”。既然不是定时任务,那么肯定是被监听对象主动告诉监听器的!
是发布thinkphp网站源码不是觉得很荒唐?被监听对象主动告诉Listener(对象方法调用),那还叫监听器?监听个鬼哦...
但是大家有没有想过,监听器英文名叫Listener,我翻译成“倾听者”有何不可?就是说,Listener对象一直在侧耳倾听,等待被监听对象发号施令。这个翻译骚不骚?
也就是说:被监听对象发生某个行为时,会主动告诉Listener(对象方法调用),让它执行对应的特定操作!
代码结构
被监听对象
监听器接口
事件(就是包装后的被监听对象)
测试
监听器案例
明天写
Servlet源码和Tomcat源码解析
画的不好,请将就。
我一般用的IDEA,很久没用Eclipse了,所以刚开始怎么继承不了HttpServlet类,然后看了一眼我创建的是Maven项目,然后去Maven仓库粘贴了Servlet的坐标进来。
maven坐标获取,直接百度maven仓库,选择第二个。
然后搜索Servlet选择第二个。
创建一个类,不是接口,继承下HttpServlet。
Servlet接口包括:init()、service()、destroy()和getServletInfo()。其中init()方法负责初始化Servlet对象,容器创建好Servlet对象后会调用此方法进行初始化;service()方法处理客户请求并返回响应,容器接收到客户端要求访问特定的Servlet请求时会调用此方法;destroy()方法负责释放Servlet对象占用的资源;getServletInfo()方法返回一个字符串,包含Servlet的创建者、版本和版权等信息。
ServletConfig接口包含:getServletName()、getServletContext()、getInitParameter(String var1)和getInitParameterNames()。其中getServletName()用于获取Servlet名称,getServletContext()获取Servlet上下文对象,getInitParameter(String var1)获取配置参数,getInitParameterNames()返回所有配置参数的名字集合。
GenericServlet抽象类实现了Servlet接口的同时,也实现了ServletConfig接口和Serializable接口。它提供了一个无参构造方法和一个实现init()方法的构造方法。GenericServlet中的init()方法保存了传递的ServletConfig对象引用,并调用了自身的无参init()方法。它还实现了service()方法,这是Servlet接口中的唯一没有实现的抽象方法,由子类具体实现。
HttpServlet是Servlet的默认实现,它是与具体协议无关的。它继承了GenericServlet,并实现了Servlet接口和ServletConfig接口。HttpServlet提供了一个无参的init()方法、一个无参的destroy()方法、一个实现了getServletConfig()方法的方法、一个返回空字符串的getServletInfo()方法、以及一个实现了service()方法的抽象方法。service()方法的实现交给了子类,以便在基于HTTP协议的Web开发中具体实现。
Tomcat的底层源码解析如下:
Server作为整个Tomcat服务器的代表,包含至少一个Service组件,用于提供特定服务。配置文件中明确展示了如何监听特定端口(如)以启动服务。
Service是逻辑功能层,一个Server可以包含多个Service。Service接收客户端请求,解析请求,完成业务逻辑,然后将处理结果返回给客户端。Service通常提供start方法打开服务Socket连接和监听服务端口,以及stop方法停止服务并释放网络资源。
Connector称为连接器,是Service的核心组件之一。一个Service可以有多个Connector,用于接收客户端请求,将请求封装成Request和Response,然后交给Container进行处理。Connector完成请求处理后,将结果返回给客户端。
Container是Service的另一个核心组件,按照层级有Engine、Host、Context、Wrapper四种。一个Service只有一个Engine,它是整个Servlet引擎,负责执行业务逻辑。Engine下可以包含多个Host,一个Tomcat实例可以配置多个虚拟主机,默认情况下在conf/server.xml配置文件中定义了一个名为Catalina的Engine。Engine包含多个Host的设计使得一个服务器实例可以提供多个域名的服务。
Host代表一个站点,可以称为虚拟主机,一个Host可以配置多个Context。在server.xml文件中的默认配置为appBase=webapps,这意味着webapps目录中的war包将自动解压,autoDeploy=true属性指定对加入到appBase目录的war包进行自动部署。
Context代表一个应用程序,即日常开发中的Web程序或一个WEB-INF目录及其下面的web.xml文件。每个运行的Web应用程序最终以Context的形式存在,每个Context都有一个根路径和请求路径。与Host的区别在于,Context代表一个应用,如默认配置下webapps目录下的每个目录都是一个应用,其中ROOT目录存放主应用,其他目录存放子应用,而整个webapps目录是一个站点。
Tomcat的启动流程遵循标准化流程,入口是BootStrap,按照Lifecycle接口定义进行启动。首先调用init()方法逐级初始化,接着调用start()方法启动服务,同时伴随着生命周期状态变更事件的触发。
启动文件分析Startup.bat:
设置CLASSPATH和MAINCLASS为启动类,并指定ACTION为启动。
Bootstrap作为整个启动时的入口,在main方法中使用bootstrap.init()初始化容器相关类加载器,并创建Catalina实例,然后启动Catalina线程。
Catalina Lifecycle接口提供了一种统一管理对象生命周期的接口,通过Lifecycle、LifecycleListener、LifecycleEvent接口,Catalina实现了对Tomcat各种组件、容器统一的启动和停止方式。在Tomcat服务开启过程中,启动的一系列组件、容器都实现了org.apache.catalina.Lifecycle接口,其中的init()、start()和stop()方法实现了统一的启动和停止管理。
加载方法解析server.xml配置文件,加载Server、Service、Connector、Container、Engine、Host、Context、Wrapper一系列容器,加载完成后调用initialize()开启新的Server实例。
使用Digester类解析server.xml文件,通过demon.start()方法调用Catalina的start方法。Catalina实例执行start方法,包括加载server.xml配置、初始化Server的过程以及开启服务、初始化并开启一系列组件、子容器的过程。
StandardServer实例调用initialize()方法初始化Tomcat容器的一系列组件。在容器初始化时,会调用其子容器的initialize()方法,初始化子容器。初始化顺序为StandardServer、StandardService、StandardEngine、Connector。每个容器在初始化自身相关设置的同时,将子容器初始化。
tomcat反序列化漏洞(cve--)
在项目开发中,Apache Tomcat中间件的使用日益普遍。为了增强知识体系,我计划整理并复现一些关键的高危漏洞,这里先介绍CVE--漏洞的复现过程。感谢Bearcat师傅、残忆师傅和李师傅等人的指导与分享。
CVE--漏洞源于JmxRemoteLifecycleListener的反序列化功能,由于Oracle的修复未及时应用于Tomcat,导致远程代码执行风险。漏洞影响了Apache Tomcat多个版本,包括9.0.0.M1至M,8.5.0至8.5.6,以及更早版本。利用该漏洞,需要外部开启特定端口(和)来实现远程代码执行。
复现漏洞需要特定环境设置,包括Apache Tomcat 8.5.2与Jdk1.7.0_。首先在server.xml中启用JmxRemoteLifecycleListener监听功能,然后下载catalina-jmx-remote.jar和groovy-2.3.9.jar到lib目录,并修改catalina.bat脚本以限制远程访问。启动Tomcat并监听RMI服务端口,确保监听成功。
利用漏洞的方式包括在webapp目录下写文本文件验证漏洞,或通过wget/copy等命令上传恶意代码获取webshell权限。为防范此类漏洞,建议关闭相关功能,限制网络访问,并强化认证。同时,定期更新Tomcat到最新版本是有效的修复措施。
更多漏洞复现和安全实践的文章可以在我的系列链接中找到,如Tomcat PUT上传漏洞(CVE--)和Tomcat后台弱口令上传war包漏洞的复现。
org.springframework.beans.factory.BeanDefinitionStoreException
1. å¨é ç½®springå°tomcatéé¢çæ¶åæ们éè¦å ä¸ä¸ªlistenerï¼æ·»å ä¸ä¸ªlistener以åè¿è¦åè¯tomcatå»åªéæ¾springçapplicationsãxmlæ件ï¼æ以è¿è¦æ·»å ä¸ä¸ªcontext-param
å³æå¦ä¸é ç½®ï¼
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-*.xml,classpath:applicationContext*.xml
</param-value>
</context-param>
è¿æ ·tomcatå°±ç¥éå»åªéå è½½springçé ç½®æ件äº
2.å¨web.xmlä¸æ²¡æå¼å ¥applicationContext.xml,åå ¶applicationContext-*.xmlæ件ï¼æè æ¯å¼å ¥çè·¯å¾é误ã
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml, /WEB-INF/applicationContext-*.xml</param-value>
</context-param>
wrong:
<context-param><param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/com/applicationContext*.xml, /WEB-INF/com/applicationContext-*.xml</param-value>
</context-param>
Web中间件漏洞之Tomcat篇
Tomcat简介
Tomcat服务器是免费开放源代码的Web应用服务器,专为轻量级应用设计,在中小型系统和并发访问用户不多的场合广泛使用。对于新手,它可作为开发和调试JSP程序的首选服务器。运行在Windows主机上时,Tomcat作为Apache服务器的扩展独立运行,可响应HTML页面的访问请求。
远程代码执行漏洞及修复
通过构造攻击请求,利用Tomcat在Windows主机上运行且启用HTTP PUT请求方法,攻击者可以上传包含任意代码的JSP文件,从而实现任意代码执行。此漏洞影响的版本为Apache Tomcat 7.0.0至7.0.。复现步骤包括配置漏洞、开启PUT方法上传文件功能、插入相关配置文件、重启服务、通过burp抓包并修改请求方式为PUT,创建并上传包含命令执行代码的JSP文件,最后验证代码执行成功。
修复措施包括检测当前版本是否受影响并禁用PUT方法,或者更新至最新版。
后台弱口令war包部署漏洞及修复
Tomcat支持后台部署war文件,直接在web目录部署webshell。若后台管理页面存在弱口令,则攻击者可通过爆破获取密码,进而上传和执行webshell。修复方法包括在系统上以低权限运行Tomcat,创建专门的Tomcat服务用户并设置最小权限,增加本地和基于证书的身份验证,部署账户锁定机制,并针对特定目录设置最小权限访问限制,避免使用弱口令。
反序列化漏洞及修复
此漏洞与Oracle发布的mxRemoteLifecycleListener反序列化漏洞相关,由使用JmxRemoteLifecycleListener的监听功能引起。在Oracle发布修复后,Tomcat未能及时修复更新,导致远程代码执行。漏洞影响的版本包括9.0.0.M1到9.0.0.M、8.5.0到8.5.6、8.0.0.RC1到8.0.、7.0.0到7.0.、6.0.0到6.0.。复现步骤需要外部开启JmxRemoteLifecycleListener监听的端口,修改配置文件和脚本,下载并部署相关jar包,验证远程代码执行。
修复措施包括关闭JmxRemoteLifecycleListener功能或对远程端口进行网络访问控制,增加严格的认证方式,并根据官方更新相应版本。
如何在struts+spring+hibernate项目中实现对线程的监控?
在Java Web项目中,经常要在项目开始运行时启动一个线程,每隔一定的时间就运行一定的代码,比如扫描数据库的变化等等。要实现这个功能,可以现在web.xml文件中定义一个Listener,然后在这个Listener中启动一个线程,在线程里面实现功能。
1. 自定义Listener
在Struts+Spring+Hibernate的Web项目中,web.xml里面一般都会有这样的代码:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
这几句代码使得Web项目的容器(也就是Web服务器,比如Tomcat)在项目启动时实例化了一个org.springframework.web.context.ContextLoaderListener类。
类似的,我们也可以在web.xml里面自己定义一个Listener,让Web服务器去实例化:
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="/xml/ns/javaee" xsi:schemaLocation="/xml/ns/javaee /xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>com.XXX.listener.WSListener</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
在以上的web.xml文件中,我们就让Web服务器在启动时实例化我们自己定义的com.XXX.listener.WSListener类(一般自己定义的Listener类要写在org.springframework.web.context.ContextLoaderListener的后面),然后在该类中去启动线程:
public class WSListener implements ServletContextListener{
private WSThread wsThread;
@Override public void contextDestroyed(ServletContextEvent event) { // TODO Auto-generated method stub
if (wsThread != null && wsThread.isRunning){
wsThread.stopThread();
}
}
@Override public void contextInitialized(ServletContextEvent event) { // TODO Auto-generated method stub
if (wsThread == null){
wsThread = new WSThread(event);
wsThread.start();
}
}
}
Listener类是由Web服务器管理的,当Web服务器启动时,将Listener类实例化并调用其contextInitialized(ServletContextEvent event)方法,当Web服务器关闭时,调用其contextDestroyed(ServletContextEvent event)方法,因此我们可以分别在这两个方法里面实现线程的启动和结束。
2. 在Spring容器以外获得其内部的Bean的实例的引用
被启动的线程用于间隔一定的时间扫描一次数据库,找出新增加的数据。在一般的Struts+Spring+Hibernate的Web项目中,Spring容器中的Bean是由Spring容器管理的,而我们这里启动的线程并不在Spring容器中,那么怎样获得Spring容器中Bean的实例的引用进而访问数据库呢?可以使用Spring的WebApplicationContextUtils工具类,该工具类获得Spring容器的引用,再获得其内部的Bean的实例的引用。
线程的代码:
public class WSThread extends Thread{ public volatile boolean isRunning = true; // 两次扫描之间休眠的时间
public long s_time; private WebApplicationContext context; private PropService propService; private Prop prop; private Temp2Service temp2Service; private Temp2 temp2;
private TempService tempService;
ServletContextEvent event; public WSThread(ServletContextEvent e){ this.event = e; this.context = WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext()); this.propService = (PropService) context.getBean("propService"); this.temp2Service = (Temp2Service) context.getBean("temp2Service");
}
public void run(){ while (isRunning){ try { this.prop = propService.findByName("scan_time"); this.s_time = Integer.parseInt(prop.getValue())*;
sleep(s_time);
System.out.println("Run!!!!!!");
} catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace();
}
}
}
public void stopThread(){
isRunning = false;
}
public boolean isRunning(){ return isRunning;
}
}
在该线程的构造函数中,使用了从Listener传过来的ServletContextEvent变量,用该变量的getServletContext()方法,获取Web项目的servletContext,然后再以这个ServletContext作为参数,使用WebApplicationContextUtils的getRequiredWebApplicationContext()方法获取ApplicationContext对象,最后再通过ApplicationContext的getBean()方法获取到Bean的实例,实现对数据库的访问。