一、JSP介绍及原理1.1 JSP简介1.2 JSP简单入门1.3 JSP原理介绍二、JSP脚本2.1 JSP 脚本形式2.2 JSP EL表达式2.3 JSP JSTL标签三、会话跟踪技术3.1 Cookie3.2 Session
原创 0xNvyao 安全随笔
声明
请勿利用本公众号文章内的相关技术、工具从事非法测试,如因此造成一切不良后果与文章作者及本公众号无关!
本节继续来分享JavaWeb知识,说一说JSP和Java的会话技术(Cookie、Session)
目录:
一、JSP介绍及原理
1.1 JSP简介
摘自ChatGPT:JSP(JavaServer Pages)是一种用于构建动态Web页面的Java 技术。它允许开发人员在HTML页面中嵌入Java代码,使其更易于生成动态内容、访问数据库、执行业务逻辑等。当JSP页面被请求时,服务器会将其转换为Servlet,并最终生成HTML响应发送给客户端浏览器。JSP提供了一种简单且强大的方式来创建交互式的Web应用程序。
现在几乎没有公司会用JSP了(除了安全研究,还有P用么),更好的架构是前后端分离技术,但是为啥当初会产生JSP技术呢?我想原因不外乎:
早期互联网对动态Web页面的需求前后段分离架构/思想还不是主流
在这个背景下,就出现了HTML+Java柔和在一起的JSP技术,这个在当时那个年代确实是一个创新和技术突破,不过随着技术的不断更新迭代,目前前后端分离架构更符合现在的软件开发模式。
1.2 JSP简单入门
编写JSP代码分为三步:
1)添加JSP依赖
<dependencies><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!--jsp依赖--><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.1</version><scope>provided</scope></dependency></dependencies>
2)创建JSP页面
在Maven工程的webapp目录下创建jsp文件:
3)编写HTML+Java代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>JSP入门</title>
</head>
<body><h1>一个JSP入门Demo</h1><%System.out.println("当控制台看到这行打印,说明JSP页面中的java代码被执行了~~~");%>
</body>
</html>
运行访问hello.jsp
看效果:
1.3 JSP原理介绍
前面JSP简介说到,当JSP页面被请求时,服务器会将其转换为Servlet,并最终生成HTML响应发送给客户端浏览器。其实这句话就是JSP的原理,不过我们要通过源码看一下:
1)找到上面写的 hello.jsp 转换后的hello_jsp.java,位于打包目录下
2)hello_jsp.java的完整代码
注意:public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
/** Generated by the Jasper component of Apache Tomcat* Version: Apache Tomcat/7.0.47* Generated at: 2024-09-05 09:07:52 UTC* Note: The last modified time of this file was set to* the last modified time of the source file after* generation to assist with modification tracking.*/
package org.apache.jsp;import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBaseimplements org.apache.jasper.runtime.JspSourceDependent {private static final javax.servlet.jsp.JspFactory _jspxFactory =javax.servlet.jsp.JspFactory.getDefaultFactory();private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;private javax.el.ExpressionFactory _el_expressionfactory;private org.apache.tomcat.InstanceManager _jsp_instancemanager;public java.util.Map<java.lang.String,java.lang.Long> getDependants() {return _jspx_dependants;}public void _jspInit() {_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();_jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());}public void _jspDestroy() {}public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)throws java.io.IOException, javax.servlet.ServletException {final javax.servlet.jsp.PageContext pageContext;javax.servlet.http.HttpSession session = null;final javax.servlet.ServletContext application;final javax.servlet.ServletConfig config;javax.servlet.jsp.JspWriter out = null;final java.lang.Object page = this;javax.servlet.jsp.JspWriter _jspx_out = null;javax.servlet.jsp.PageContext _jspx_page_context = null;try {response.setContentType("text/html;charset=UTF-8");pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);_jspx_page_context = pageContext;application = pageContext.getServletContext();config = pageContext.getServletConfig();session = pageContext.getSession();out = pageContext.getOut();_jspx_out = out;out.write("\n");out.write("\n");out.write("<html>\n");out.write("<head>\n");out.write(" <title>JSP入门</title>\n");out.write("</head>\n");out.write("<body>\n");out.write(" <h1>一个JSP入门Demo</h1>\n");out.write(" ");System.out.println("当控制台看到这行打印,说明JSP页面中的java代码被执行了~~~");out.write("\n");out.write("</body>\n");out.write("</html>\n");} catch (java.lang.Throwable t) {if (!(t instanceof javax.servlet.jsp.SkipPageException)){out = _jspx_out;if (out != null && out.getBufferSize() != 0)try { out.clearBuffer(); } catch (java.io.IOException e) {}if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);else throw new ServletException(t);}} finally {_jspxFactory.releasePageContext(_jspx_page_context);}}
}
3)查看org.apache.jasper.runtime.HttpJspBase源码
注意:public abstract class HttpJspBase extends HttpServlet
implements HttpJspPage
4)然后仔细看上面转换的hello_jsp.java的源码
里面也包含_jspInit()、_jspService()、_jspDestroy()三个方法,这个和Servlet接口的方法类似。
其中_jspService()是由容器生成并调用的一个方法,它是JSP页面被转换成Servlet之后的核心方法之一。
分析到这就清楚了,真正给浏览器响应的不是jsp页面,而是转换后的jsp servlet对象的_jspService() 方法,这个方法主要负责处理客户端的请求并生成响应。
在这个方法中,容器会将生成的 Servlet 代码中插入 JSP 页面中的 Java 代码段,并将整个页面编译成 Servlet 类。
当客户端请求访问这个 JSP 页面时,容器会实例化这个 Servlet 类,并调用其 _jspService() 方法来处理请求。
最后贴一张图看下hello.jsp源码和转换后的hello_jsp.java源码对比:
二、JSP脚本
2.1 JSP脚本形式
介绍JSP脚本其实重点是介绍JSP脚本里如何定义Java代码。
JSP提供了三种形式:
<%...%>:内容会直接放到_jspService()方法之中<%=...>:内容会放到_jspService()方法的out.print()中,作为out.print()的参数<%!...%>:内容会放到_jspService()方法之外,被类直接包含
看一下这样的Demo,分别包含上面三种JSP脚本形式:
<body><h1>一个JSP入门Demo</h1><%System.out.println("当控制台看到这行打印,说明JSP页面中的java代码被执行了~~~");int num = 100;%><%!String username = "jsp";void myPrint(){System.out.println(username);}%><%="hello"%><%=num%><%=username%>
</body>
启动项目访问一下修改后的JSP页面,然后看下转换后的hello_jsp.java文件:
2.2 JSP EL表达式
JSP EL表达式是JSP规范中的一个特性,用于简化在JSP页面中访问Java对象和数据的语法。
EL提供了一种简洁、易读的方式来访问JavaBean组件、请求参数、会话属性等,并允许在JSP页面中执行基本的表达式和操作。
举几个例子:
<body><%--进行算数计算--%>${100+200} <br /><%--访问JavaBean属性--%>${users[1].username} <br /><%--调用方法--%>${users[1].getPassword()} <br /><%--操作集合列表--%>${users[1]} <br /><%--EL表达式隐士对象--%>${header} <br />${param} <br /><%--访问作用域对象--%>${requestScope}${requestScope.containsKey("users")} <br />${requestScope.get("javax.servlet.forward.context_path")} <br /></body>
结果:
另外值得说一下的是JSP的四大作用域,分别是page、request、session、applicaiton,从左到右作用域范围一次递增,这些作用域提供了不同级别和范围的数据共享机制,例如上面通过 req.setAttribute("users", users); 给request域设置数据。
page:当前jsp页面有效(最小)request:当前请求有效session:当前回话有效application:当前应用有效(最大)
2.3 JSP JSTL标签
JSP 标准标签库(JSTL)是一组自定义标签,用于简化 JSP 页面的开发。
标签非常多,可以通过这里学习,这里只介绍下最重要的两个标签:c:if
和 c:forEach
JSTL标签学习
https://www.runoob.com/jsp/jsp-jstl.html
1)引入jstl坐标
<!--jstl标签库--><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.1.2</version></dependency><dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version></dependency>
2)添加 JSTL(JSP Standard Tag Library)核心标签库指令
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
# 添加下面这行指令
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>JSTL入门</title>
</head>
3)编写servlet和jsp
@WebServlet("/myServlet")
public class MyServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setAttribute("gender", 1);req.getRequestDispatcher("jstl-demo.jsp").forward(req, resp);
}<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>JSTL入门</title>
</head>
<body>
<%--jstl的c:if标签用于条件判断,替换java的if else--%><c:if test="${gender == 1}">男</c:if><c:if test="${gender == 0}">女</c:if>
</body>
</html>
4)访问
5)再来看下 c:forEach
标签
特别适合对集合数据进行循环遍历,替代了jsp中繁琐的java代码
<body><%--jstl的c:forEach标签用于循环,替换java的foreach循环--%><c:forEach items="${users}" var="user"><tr align="center"><td>${user.username}</td><td>${user.password}</td><td>${user.address}</td><c:if test="${user.gender == 1}"><td>男</td></c:if><c:if test="${user.gender == 0}"><td>女</td></c:if><br /></tr></c:forEach>
</body>
三、会话跟踪技术
在本系列第一篇文章有介绍过HTTP协议的特点,其中重要的一点是HTTP是无状态的:
在 Web 开发中,HTTP 协议是无状态的,即每个请求都是相互独立的,服务器无法直接识别来自同一用户的多个请求。
为了解决这个问题,会话跟踪技术被引入,以维护用户和服务器之间的关联状态。
会话跟踪技术的实现分为客户端会话跟踪技术Cookie和服务端会话跟踪技术Session,下面分别来介绍。
3.1 Cookie
Cookie应该都很熟悉了,是一种客户端会话跟踪技术,将数据保存到客户端,以后浏览器每次请求都携带Cookie数据进行访问,服务端来校验Cookie数据。
1)创建一个新的 Maven Module
2)编写创建 Cookie 的 Servlet
@WebServlet("/cookie1")
public class SetCookieServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//1. 创建cookie对象Cookie cookie = new Cookie("whoami", "zhangsan");//2. 发送cookieresponse.addCookie(cookie);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
}
3)启动项目,浏览器查看 set-cookie
4)编写获取 Cookie 的 Servlet
@WebServlet("/cookie2")
public class GetCookieServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//1. 获取cookie数组Cookie[] cookies = request.getCookies();//2. 遍历数组for (Cookie cookie : cookies) {//3. 获取数据String name = cookie.getName();if ("whoami".equals(name)) {String value = cookie.getValue();System.out.println(name + ":" + value);}}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
}
5)访问/cookie2,查看浏览器携带的cookie
服务端正常拿到并解析了cookie:
当然cookie还有一些常用属性,这里介绍下setHttpOnly和setMaxAge:
//1. 创建cookie对象
Cookie cookie = new Cookie("whoami", "zhangsan");
cookie.setMaxAge(60 * 60 * 24); // 一天时间
cookie.setHttpOnly(true);
3.2 Session
Session是服务端会话跟踪技术,将数据保存在服务端。
客户端仅保存会话标识符(通过是一个Cookie)。
1)编写存储Session的Servlet
@WebServlet("/session1")
public class SetSessionServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//1. 获取Session对象HttpSession session = request.getSession();//2. 存储数据session.setAttribute("whoami","i am session");}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
}
2)编写获取Session数据的Servlet
@WebServlet("/session2")
public class GetSessionServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//1. 获取Session对象HttpSession session = request.getSession();//2. 获取数据Object whoami = session.getAttribute("whoami");System.out.println(whoami);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {this.doGet(request, response);}
}
3)重启服务测试
访问 /session1
访问 /session2
最后来总结一下Session的原理,前面我们提到Session会话技术是用来解决HTTP无状态的,也就是说解决多次请求之间数据共享的问题,那么思考一下上面的Session是如何解决的呢?
其实就是一句话Session是基于Cookie实现的。
在上面的SetSessionServlet.java 和 GetSessionServlet.java 中分别打印如下内容:
System.out.println("SetSessionServlet设置的session对象地址:" + session); //打印session对象内存中的地址
System.out.println("SetSessionServlet设置的session对象ID:" + session.getId()); //打印session id
然后再重启项目分别访问 /session1
和 /session2
:
通过打印内容可以知道:
1、会话的创建:
当用户第一次访问网站时,服务器会为该用户创建一个唯一的会话ID,并将相关数据存储到会话对象中会话ID通常以Cookie的形式存储在用户的浏览器中
2、会话的跟踪:
当用户在网站上浏览不同页面时,浏览器会将包含会话ID的Cookie随每个请求发送给服务器
服务器通过解析会话ID,可以识别用户会话,并将用户的相关信息存储在会话对象中
在这个过程中,服务器通过会话ID来标识和区分不同用户的会话,确保用户在网站上的各个请求之间保持一致的状态。Session会话技术的实现通常依赖于服务器端的会话管理机制,用于存储和管理用户会话的相关数据。通过这种方式,同一个会话的用户可以在整个访问过程中保持一致的状态和体验。
以上本篇就介绍了JSP技术和会话管理技术,这些技术点会在后续的整合案例里面都实现一遍。