会话技术
会话跟踪技术概述
会话:是指浏览器和服务器之间的一段通信过程。从用户访问网站开始,到关闭浏览器或者任一方断开连接为止,称为一次会话。在一次会话中,浏览器和服务器之间可以多次发送请求和响应。
- 建立过程: 当浏览器向服务器发送请求,并收到响应后,会话就建立了。
- 持续时间: 只要浏览器和服务器没有断开连接,会话就持续存在。
- 多次交互: 在同一会话中,可以进行多次请求与响应。
会话跟踪:是一种技术,用来维护会话中的数据状态。服务器需要识别多个请求是否来自同一个用户,并在这些请求之间共享数据。
- 当一个用户在同一会话中多次发送请求时,服务器可以通过会话跟踪识别这些请求来自同一个用户。
- 识别后,服务器能够在多次请求之间共享数据,从而实现复杂功能。
- 场景举例:
- 购物车功能:
第一次请求:将商品加入购物车。
第二次请求:进入购物车页面。
要显示加入的商品(即后一次请求要想展示前一次请求所添加的商品),就需要共享数据。 - 用户登录信息展示:
用户登录后,每次请求都会显示用户名(例如京东或百度)。 - 登录时的“记住我”功能:
勾选“记住我”后,下次登录时会自动填充用户名和密码。 - 图形验证码功能:
第一次请求生成图形验证码。
第二次请求提交图形验证码。
两次请求之间需要对验证码进行比对。
- 购物车功能:
为什么浏览器和服务器不能直接共享数据?
因为HTTP协议是无状态的:
- 每次浏览器向服务器发送请求时,服务器都将其视为全新的请求。
- 无状态设计的优点是每个请求独立互不影响,但缺点是无法实现请求之间的数据共享。
会话跟踪的实现方式
为了实现会话跟踪,有两种主要技术:
- 客户端会话跟踪技术:Cookie,数据存储在 浏览器端。
- 服务端会话跟踪技术:Session,数据存储在 服务器端。
Cookie 技术详解
Cookie: 是一种客户端会话跟踪技术,将数据存储在客户端(浏览器)。
在后续的请求中,浏览器会携带这些数据访问服务器,从而实现数据共享。
Cookie 的工作流程
- 浏览器向服务端发送请求。
- 服务端接收请求并生成一个 Cookie 对象,将数据存入其中。
- 服务端通过 HTTP 响应头将 Cookie 发送到浏览器。
- 浏览器接收 Cookie 并将其存储在内存或硬盘中(此时浏览器和服务端就建立了一次会话)。
- 在后续请求中,浏览器再次发送HTTP请求给服务端时,会自动将 Cookie 对象中的所有数据携带到服务器。
- 服务器通过这些 Cookie 数据识别用户,从而共享会话中的数据。
其中,数据包括用户身份数据、会话管理数据、偏好设置数据等。
Cookie 的基本使用
对于Cookie的操作主要分两大类,分别是发送Cookie和获取Cookie。
发送 Cookie
通过服务端代码发送 Cookie 到浏览器:
/* by 01022.hk - online tools website : 01022.hk/zh/regexdso.html */
// 创建 Cookie 对象,并设置数据,第一个参数是Cookie的名称,第二个参数是Cookie的值
Cookie cookie = new Cookie("key", "value");
// 使用 response 对象发送 Cookie
response.addCookie(cookie);
// 设置 Cookie 存活时间(可选)
cookie.setMaxAge(60 * 60 * 24); // 1天
案例:发送 Cookie
需求:在Servlet中生成Cookie对象并存入数据,然后将数据发送给浏览器
具体实现步骤:
- 创建Maven项目,项目名称为cookie-demo,并在pom.xml添加依赖
- 编写Servlet类,名称为
AServlet
- 在AServlet中创建Cookie对象,存入数据,发送给前端
- 启动测试,在浏览器查看Cookie对象中的值
具体实现代码:
- 创建编写Servlet类
AServlet
,并将数据存入 Cookie 对象并发送到浏览器(前端):/* by 01022.hk - online tools website : 01022.hk/zh/regexdso.html */ @WebServlet("/aServlet") public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //发送Cookie // 1. 创建一个 Cookie 对象,名称为 "username",值为 "user123" Cookie cookie = new Cookie("username", "user123"); // 2. 将创建的 Cookie 添加到 HTTP 响应中,以便发送到客户端浏览器 response.addCookie(cookie); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
- 访问 Servlet 后,在浏览器开发者工具中查看 Cookie:
- 方式一:右上角三个点的图标 → 设置 → 隐私设置和安全性 → Cookie及其他网站数据 → 查看所有Cookie和网站数据 → localhost
- 方式二: F12 或 Ctrl+Shift+I → Application → Cookie
获取 Cookie
通过服务端代码获取浏览器发送的 Cookie:
// 使用request对象获取客户端携带的所有 Cookie 对象
Cookie[] cookies = request.getCookies();
// 遍历 Cookie 数组,获取每一个Cookie对象
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
}
案例:获取 Cookie
需求:在Servlet中获取前一个案例存入在 Cookie 对象中的数据
具体实现步骤:
- 编写一个新Servlet类,名称为
BServlet
- 在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值
- 启动测试,在控制台打印出获取的值
具体实现代码:
- 创建编写Servlet类
BServlet
,从请求中提取 Cookie 数据:@WebServlet("/bServlet") public class BServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 从请求中获取所有的Cookie Cookie[] cookies = request.getCookies(); // 遍历Cookie数组 for (Cookie cookie : cookies) { // 检查Cookie的名称是否为"username" if ("username".equals(cookie.getName())) { // 如果是,获取Cookie的值 String value = cookie.getValue(); // 打印获取到的Cookie值 System.out.println("Cookie Value: " + value); // 打印获取的值 } } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
- 访问
BServlet
,在控制台查看打印的 Cookie 值。
Cookie 的原理分析
Cookie 的底层实现基于 HTTP 协议,涉及以下两个请求头:
- 响应头:
Set-Cookie
- 服务器通过
Set-Cookie
响应头发送 Cookie 数据。
- 服务器通过
- 请求头:
Cookie
- 浏览器在后续请求中,通过
Cookie
请求头携带数据。
- 浏览器在后续请求中,通过
详析
-
AServlet 设置并发送 Cookie:当后端的
AServlet
需要向前端发送 Cookie 时,它通过Set-Cookie
将数据(如Set-Cookie: username=zs
)传递给浏览器。 -
浏览器接收并存储 Cookie:浏览器收到响应后,会从响应头中提取
Set-Cookie
的值,并将其存储在浏览器内存或本地(根据 Cookie 的配置)。 -
浏览器发送 Cookie 给 BServlet:当浏览器再次向服务器(如
BServlet
)发送请求时,浏览器会自动在请求头中附带存储的 Cookie 数据(如Cookie: username=zs
)Tomcat 在 Cookie 过程中的作用
- Tomcat 会将请求头中所有的 Cookie 数据解析并封装成
Cookie[]
数组,并加到 HTTP 响应头中。 - 通过
Request
对象,BServlet
可以获取到这个数组,并从中提取需要的值(如username=zs
)。
验证方法:
- 访问AServlet对应的地址 http://localhost:8080/应用路径/aServlet ,使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行 查看响应头中的数据
- 访问BServlet对应的地址 http://localhost:8080/应用路径/bServlet ,使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行 查看请求头中的数据
Cookie 的存活时间
默认情况下,Cookie 存储在浏览器内存中,浏览器关闭后,内存释放,Cookie 就会被删除。
为了实现持久化存储,可以通过 setMaxAge(int seconds)
设置存活时间:
- 正数: 持久化存储,单位为秒。
- 负数(默认): 存储在内存中,浏览器关闭后删除。
- 零: 删除对应的 Cookie。
案例:设置 Cookie 存活时间
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//发送 Cookie
Cookie cookie = new Cookie("username", "user123"); //创建Cookie对象
cookie.setMaxAge(60 * 60 * 24 * 7); // 设置为 7 天,这样写易阅读,自动计算
//cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算
// 将创建的Cookie添加到响应中,这样客户端在接收到响应时会存储这个Cookie
response.addCookie(cookie);
}
验证:
- 设置存活时间后,浏览器关闭再打开,Cookie 依然存在。
- 通过浏览器查看Cookie的内容,会发现Cookie的相关信息。
Cookie 存储中文
默认情况下,Cookie 不支持直接存储中文字符。存储中文时需要使用 URL 编码 和 解码:
- 存储时编码: 使用
URLEncoder.encode()
将中文编码为 URL 格式。 - 读取时解码: 使用
URLDecoder.decode()
将编码值解码为中文。
案例:存储和获取中文 Cookie
-
在AServlet中对中文进行URL编码以 存储中文:
@WebServlet("/aServlet") public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //发送Cookie String value = "张三"; value = URLEncoder.encode(value, "UTF-8"); //对中文进行URL编码 System.out.println("存储数据:"+value); Cookie cookie = new Cookie("username",value); //将编码后的值存入Cookie中 cookie.setMaxAge(60*60*24*7); //设置存活时间 ,1周 7天 response.addCookie(cookie); //发送Cookie,将创建的 Cookie 对象添加到 HTTP 响应中 } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
)在BServlet中获取值,并对值进行解码以 获取中文:
@WebServlet("/bServlet") public class BServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取Cookie // 从请求对象中获取所有的 Cookie,返回一个 Cookie 数组 Cookie[] cookies = request.getCookies(); // 遍历获取到的 Cookie 数组 for (Cookie cookie : cookies) { // 获取数据 String name = cookie.getName(); // 检查当前 Cookie 的名称是否为 "username" if ("username".equals(cookie.getName())) { // 获取当前 Cookie 的值,即URL编码后的值 String value = cookie.getValue(); // 通过 URLDecoder 进行解码 value = URLDecoder.decode(value,"UTF-8"); // 或直接两步合一步:String value = URLDecoder.decode(cookie.getValue(), "UTF-8"); System.out.println("Decoded Value: " + value); // 输出 "张三" } } // 结束条件检查和循环,所有 Cookies 都会被依次遍历并处理。 } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
Session 技术详解
Session:是一种服务端会话跟踪技术,用于在一次会话的多次请求之间共享数据。
与 Cookie 的区别
- 存储位置:Session 将数据存储在服务端,而 Cookie 是存储在客户端。
- 安全性:由于存储在服务端,Session 相比 Cookie 更加安全。
Session 的工作流程
- 用户第一次访问服务器后,服务器会创建一个新的 Session 对象并存储数据。
- 新 Session 创建时,服务器会生成一个唯一标识 (Session ID),并通过 HTTP 响应头将其发送给浏览器,存储在浏览器的 Cookie 中(默认名为
JSESSIONID
)。 - 浏览器在后续请求中自动携带该 Cookie 信息,使服务器能找到对应的 Session 对象,实现数据共享。
Session 的基本使用
核心方法:
- 获取 Session 对象:
HttpSession session = request.getSession();
- 存储数据到 session 域中:
session.setAttribute("key", value);
- 根据 key,获取数据:
Object value = session.getAttribute("key");
- 根据 key,删除数据:
session.removeAttribute("key");
案例:在 Servlet 中存取数据
需求:在一个 Servlet 中存储数据,在另一个 Servlet 中获取数据。
具体实现步骤:
- 编写第一个Servlet类,名称为
SessionDemo1
,用于存储数据到Session中。 - 在SessionDemo1的doGet或doPost方法中,通过
request.getSession()
方法获取Session对象。 - 使用Session对象的
setAttribute(String name, Object value)
方法存储数据。 - 编写第二个Servlet类,名称为
SessionDemo2
,用于从Session中获取数据。 - 在SessionDemo2的doGet或doPost方法中,同样通过
request.getSession()
方法获取Session对象。 - 使用Session对象的
getAttribute(String name)
方法获取存储的数据。 - 在SessionDemo2中,将获取到的值打印到控制台或进行其他处理。
- 配置web.xml文件,映射SessionDemo1和SessionDemo2的URL路径。
- 启动Web服务器,访问SessionDemo1的URL,使其设置Session数据。
- 访问SessionDemo2的URL,观察控制台输出或处理结果。
具体实现代码:
-
创建
SessionDemo1
(存储数据):@WebServlet("/demo1") public class SessionDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); // 获取 Session 对象 session.setAttribute("username", "zs"); // 存储数据 } }
-
创建
SessionDemo2
(获取数据):@WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); // 获取 Session 对象 Object username = session.getAttribute("username"); // 获取数据 System.out.println(username); } }
-
SessionDemo1
:获取Session对象、存储数据:@WebServlet("/demo1") public class SessionDemo1 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //存储到Session中 //1. 获取Session对象 HttpSession session = request.getSession(); //2. 存储数据 session.setAttribute("username","zs"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
SessionDemo2
:获取Session对象、获取数据:@WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取数据,从session中 //1. 获取Session对象 HttpSession session = request.getSession(); //2. 获取数据 Object username = session.getAttribute("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
测试:
- 先访问
http://localhost:8080/cookie-demo/demo1
将数据存入 Session。 - 再访问
http://localhost:8080/cookie-demo/demo2
从 Session 中获取数据并输出到控制台。 - 查看控制台
- 先访问
注意:Session中可以存储的是一个Object类型的数据,也就是说Session中可以存储任意数据类型。
Session 的原理分析
Session 实现数据共享原理
Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个。
Session是基于Cookie来实现的
详细流程
- 首次请求创建Session
- 用户访问
demo1
时,Tomcat生成Session对象并分配唯一ID(如id:10
)。 - Session数据(如用户信息)存储于服务器内存或数据库中。
- 用户访问
- 响应中添加Session ID
- Tomcat在响应头中添加
Set-Cookie: JSESSIONID=10
,告知浏览器保存此ID。 - 示例:Chrome开发者工具的“网络”标签中可查看此响应头。
- Tomcat在响应头中添加
- 浏览器存储Cookie
- 浏览器将
JSESSIONID=10
存入内存(未设置过期时间时),并在同一会话的后续请求中自动附加此Cookie。
- 浏览器将
- 后续请求身份识别
- 访问
demo2
时,请求头携带Cookie: JSESSIONID=10
,Tomcat据此查找对应Session。 - 若找到
id:10
的Session,直接复用数据;否则新建Session(如ID失效或首次访问)。
- 访问
- 会话终止与重建
- 关闭浏览器:内存中的Cookie被清除,后续请求无
JSESSIONID
,服务器创建新Session。 - 超时机制:Session默认有效期(如30分钟),无活动则自动销毁。
- 关闭浏览器:内存中的Cookie被清除,后续请求无
demo1
和demo2
代表服务器上的两个不同资源(如Servlet、API接口或页面)
查看响应头Set-Cookie图示
(1)使用chrome浏览器访问http://localhost:8080/cookie-demo/demo1 ,打开开发者模式(F12或Ctrl+Shift+I),查看响应头数据:
(2)使用chrome浏览器再次访问http://localhost:8080/cookie-demo/demo2 ,查看请求头数据:
Session 的细节
只要服务器是正常关闭和启动,session中的数据是可以被保存下来的。
Session 的钝化与活化
-
钝化:服务器正常关闭时,Tomcat会自动将Session 数据会被保存到硬盘的文件中 (
SESSIONS.ser
)。
钝化的数据路径为:项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser
-
活化:服务器正常重启时,从文件中将 Session 数据加载到Session中。
数据加载到Session中后,路径中的SESSIONS.ser
文件会被删除掉
注意:这里所说的关闭和启动应该要确保是正常的关闭和启动。
正常关闭Tomcat服务器方式之一: 使用命令行的方式来启动和停止Tomcat服务器
启动:进入到项目pom.xml所在目录,执行 tomcat7:run
停止:在启动的命令行界面,键入Ctrl
+C
补充:
- 上述方法仅适用于开发/测试环境
- 启动命令
tomcat7:run
需 依赖环境: 该命令属于 Maven的Tomcat7插件(tomcat7-maven-plugin
),项目中需在pom.xml
声明插件依赖
测试流程:
- 先启动Tomcat服务器
- 访问http://localhost:8080/cookie-demo/demo1 ,将数据存入session中
- 正确停止Tomcat服务器
- 再次重新启动Tomcat服务器
- 访问http://localhost:8080/cookie-demo/demo2 查看是否能获取到session中的数据
经过测试,会发现只要服务器是正常关闭和启动,session中的数据是可以被保存下来的。
Session数据 与 Session ID 及 Cookie 之间关系
[首次请求] → [服务端生成 Session 和 Session ID] → [通过 Cookie 返回 Session ID 给客户端并储存]
[后续请求] → [客户端发送 Cookie 携带 Session ID] → [服务端读取对应 Session 数据]
一、协作机制
- 用户的多个数据Session 依赖 Cookie 关联(默认)
- Session ID 传递:服务端生成 Session 数据及唯一 ID,通过
Set-Cookie
返回 ID 给客户端。 - 客户端存储:浏览器将 Session ID 保存在 Cookie(如
JSESSIONID
),后续请求自动携带。 - 服务端关联:服务端通过 Session ID 找到对应的 Session 数据。
- 角色分工
- Cookie:仅存储身份标识 Session ID(在客户端)。
- Session:存储用户状态数据(在服务端)。
- 依赖关系:Session 依赖 Cookie 传递 ID,无 Cookie 则无法关联会话。
二、生命周期对比
机制 | 默认行为 | 长期保存条件 | 失效场景 |
---|---|---|---|
Session | 服务端内存中,随服务器重启丢失 | 同时满足: 1. 服务端数据持久化(如 Redis、数据库) 2. 客户端 Cookie 持久化(保留 Session ID) | 1. 服务端主动销毁 2. Session 超时(服务端配置) 3. 持久化存储故障 |
Cookie | 会话级(浏览器关闭失效) | 显式设置:Expires (绝对时间)或 Max-Age (相对时间) | 1. Cookie 过期 2. 用户手动清除 3. 浏览器隐私模式 |
Session ID | 关联同一会话,依赖 Cookie 存储 | 同 Cookie 需客户端 Cookie 持久化 + 服务端 Session 持久化 | 1. Cookie 被清除或过期 2. 服务端 Session 被销毁 |
例外情况:Tomcat 的 Session 钝化与活化
-
触发条件
- 钝化:仅在 Tomcat 正常关闭时,内存中的 Session 会被序列化到磁盘(
work
目录下的.ser
文件)。 - 活化:Tomcat 重启后,自动从磁盘加载 Session 数据到内存。
- 钝化:仅在 Tomcat 正常关闭时,内存中的 Session 会被序列化到磁盘(
-
注意事项
- 非持久化场景:若服务器异常崩溃或强制终止,Session 数据仍会丢失。
- 生产环境限制:
- 仅适用于单机部署,不适用于分布式集群(Session 无法跨节点共享)。
- 默认存储机制效率低,建议替换为 Redis 等外部存储。
补充说明
-
安全性权衡
- 长期保存 Session ID 可能引发 Session 固定攻击(Session Fixation)。建议:
- 用户登录后生成新的 Session ID。
- 绑定用户 IP/User-Agent 等指纹信息。
- 设置较短的 Session 超时时间,结合 Refresh Token 机制。
- 优先通过 Token 机制(如 JWT)实现长期会话,而非过度依赖延长 Session 生命周期。
- 长期保存 Session ID 可能引发 Session 固定攻击(Session Fixation)。建议:
-
浏览器隐私模式的影响
- 隐私模式(无痕浏览)下,浏览器关闭后所有 Cookie(包括持久化 Cookie)会被清除,Session ID 随之失效。
Session 的销毁
Session的销毁有两种主要方式:自动过期销毁和手动调用方法销毁。以下为详细说明:
1. 自动过期销毁
默认行为:Session在用户无操作的30分钟后自动销毁。
修改失效时间:
通过项目中的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_3_1.xsd"
version="3.1">
<session-config>
<session-timeout>100</session-timeout> <!-- 单位:分钟 -->
</session-config>
</web-app>
- 优先级:项目中的
web.xml
会覆盖Tomcat的默认配置。 - Tomcat默认值:若未配置,默认30分钟(定义在Tomcat的
web.xml
中,无需修改)。
2. 手动销毁
调用invalidate()
方法立即销毁当前Session,常用于用户退出登录。
代码示例(在SessionDemo2类中添加session销毁的方法):
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 获取Session对象
HttpSession session = request.getSession();
// 2. 销毁Session
session.invalidate();
// 3. 尝试获取数据(会抛出异常!)
Object username = session.getAttribute("username"); // IllegalStateException
}
}
注意事项:
- 调用
invalidate()
后,Session立即失效,继续操作其属性会抛出IllegalStateException
。 - 典型应用场景:用户注销时销毁Session,确保数据安全。
访问测试:先访问demo1将数据存入到session,再次访问demo2从session中获取数据,访问网页如图所示\
Cookie与Session对比
核心概念
-
Cookie
- 定义:客户端(浏览器)存储的小型文本数据(键值对),由服务器生成并发送给客户端保存。
- 作用:在无登录状态下实现用户身份识别或状态跟踪。
- 特点:存储在客户端,可设置长期有效,安全性较低。
- 注意事项:敏感信息避免明文存储,应加密或哈希处理;使用
Secure
属性(仅HTTPS传输)和HttpOnly
属性(禁止JS访问)。
-
Session
- 定义:服务端存储的会话数据,通过唯一的Session ID与客户端关联。
- 作用:保存用户登录后的敏感信息(如用户ID、权限)。
- 特点:存储在服务端,默认30分钟失效,安全性高。
- 注意事项:及时销毁,用户退出时调用
session.invalidate()
;超时配置,通过web.xml
或框架配置调整失效时间。
协作关系(Session依赖Cookie)
Session ID默认通过名为JSESSIONID
的Cookie传递。若客户端禁用Cookie,需通过URL重写(如response.encodeURL()
)传递Session ID。
对比总结
特性 | Cookie | Session |
---|---|---|
存储位置 | 客户端(浏览器) | 服务端(服务器内存或数据库) |
安全性 | 低(数据明文存储) | 高(仅存储Session ID在客户端) |
数据大小限制 | 单个Cookie ≤ 4KB,域名下最多20个左右 | 无限制(依赖服务器资源) |
生命周期 | 可设置setMaxAge() 长期保留 | 默认30分钟(可配置),浏览器关闭失效 |
性能影响 | 不占用服务器资源 | 占用服务器内存或存储空间 |
应用场景
-
Cookie的典型场景
- 记住我功能:长期保存用户登录状态(如“下次自动登录”)。
- 用户偏好设置:存储语言、主题等个性化配置。
- 未登录购物车:临时保存用户未登录时的购物车信息。
-
Session的典型场景
- 用户登录信息:存储用户ID、角色权限等敏感数据。
- 验证码校验:服务端保存生成的验证码,与用户输入对比。
- 临时会话数据:如多步骤表单填写过程中的中间数据。
如何选择?
- 用Cookie:需长期保存的非敏感数据(如用户偏好)。
- 用Session:短期且敏感的数据(如登录状态)。
- 结合使用:例如用Cookie保存用户昵称(非敏感),用Session保存用户ID(敏感)。两者结合可构建安全高效的会话管理机制。实际开发中需根据业务需求权衡选择。
示例代码片段
- Cookie操作(Java)
// 创建Cookie
Cookie cookie = new Cookie("theme", "dark");
cookie.setMaxAge(60 * 60 * 24 * 7); // 保存7天
response.addCookie(cookie);
// 读取Cookie
Cookie[] cookies = request.getCookies();
for (Cookie c : cookies) {
if ("theme".equals(c.getName())) {
String theme = c.getValue();
}
}
- Session操作(Java)
// 存储数据
HttpSession session = request.getSession();
session.setAttribute("user", loggedInUser);
// 读取数据
User user = (User) session.getAttribute("user");
// 销毁Session
session.invalidate(); // 用户退出时调用
用户登录注册案例
需求分析
- 成功登录:跳转列表页,显示当前用户名。
- 失败登录:返回登录页,显示错误信息。
- 记住用户:勾选后,下次访问登录页面自动填充用户名密码(Cookie实现)。
- 完成注册功能,并实现验证码功能


技术栈
- 前端:HTML + JSP
- 后端:Servlet + Service + Dao
- 数据库:MySQL + MyBatis
用户登录功能
流程总览
前端表单 → Web层(Servlet) → Service层 → Dao层(Mapper) → 数据库
数据逐层传递,结果逐层返回,最终由Web层处理响应。
分步解析
- 前端提交请求:用户填写表单、点击登录后,表单通过HTTP请求将数据(
username
、password
)提交至后端LoginServlet
。 - Web层接收数据:以
LoginServlet
为入口接收请求参数,从中提取用户名和密码,然后调用Service层处理业务逻辑。 - Service层处理业务逻辑:由
UserService
类负责协调数据与业务规则;实现login()
方法,调用Dao层查询数据库,仅进行逻辑调度,不直接操作数据库。 - Dao层访问数据库:
UserMapper
接口定义数据库操作,声明SQL查询方法(如selectByUsernameAndPassword
),将查询结果封装为User
对象返回给Service层。 - 数据逐层返回:数据按 “数据库 → Dao层 → Service层 → Web层” 的路径逐层返回。查询成功时返回包含用户数据的
User
对象,查询失败时返回null
。 - Web层处理响应
- 成功登录:将
User
对象存入Session(维持登录状态),重定向至列表页,页面显示用户名(从Session读取)。 - 登录失败:在请求中存入错误信息(如“用户名或密码错误”),转发回登录页,前端显示错误提示。
流程图
具体实现
一、 完成Dao层的代码编写
- 在 com.itheima.mapper 包下创建 UserMapper 接口,用于根据用户名、密码查询用户对象:
public interface UserMapper {
/**
* 根据用户名和密码查询用户对象
* @param username
* @param password
* @return 匹配的用户对象,若未找到返回null
*/
@Select("select * from tb_user where username = #{username} and password = #{password}")
User select(@Param("username") String username,@Param("password") String password);
/**
* 根据用户名查询用户对象
* @param username
* @return 用户对象,未找到时返回null
*/
@Select("select * from tb_user where username = #{username}")
User selectByUsername(String username);
/**
* 添加用户
* @param user 用户实体对象(属性自动映射到SQL参数)
*/
@Insert("insert into tb_user values(null,#{username},#{password})")
void add(User user);
}
- 在 com.itheima.pojo 包下创建 User类,用于封装用户数据(标准 Java Bean):
// 定义User类,用于映射数据库中的用户表
public class User {
// 用户ID(整数类型,对应数据库主键)
private Integer id;
// 用户名(字符串类型)
private String username;
// 密码(字符串类型)
private String password;
// 获取用户ID
public Integer getId() {
return id;
}
// 设置用户ID
public void setId(Integer id) {
this.id = id; // this.id 指当前对象的属性,id 是传入参数
}
// 获取用户名
public String getUsername() {
return username;
}
// 设置用户名
public void setUsername(String username) {
this.username = username; // 将参数值赋给当前对象的属性
}
// 获取密码
public String getPassword() {
return password;
}
// 设置密码
public void setPassword(String password) {
this.password = password;
}
// 重写toString()方法:返回对象的字符串表示形式
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' + // \' 转义单引号
", password='" + password + '\'' +
'}';
}
}
- 位于
resources/com/itheima/mapper
目录,是 MyBatis 的核心 SQL 映射文件。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" <!-- 文档类型定义:MyBatis官方约束 -->
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- DTD文件位置(校验XML结构) -->
<!-- 4. mapper根标签:定义SQL映射 -->
<!-- namespace:必填项,绑定对应Mapper接口的全限定名 -->
<mapper namespace="com.itheima.mapper.UserMapper">
<!-- 后续在此处添加SQL语句 -->
</mapper>
二、完成Service层的代码编写
在 com.itheima.service 包下,创建UserService类,负责用户登录业务逻辑处理:
public class UserService {
//1.使用工具类获取SqlSessionFactory
SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 用户登录验证方法
* @param username 输入的用户名
* @param password 输入的密码
* @return 验证成功的User对象(失败返回null)
*/
public User login(String username,String password){
//2. 获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//3. 获取UserMapper接口的代理实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//4. 调用Mapper接口方法执行SQL查询
User user = mapper.select(username, password);
//释放资源
sqlSession.close();
return user;
}
}
三、完成页面和Web层的代码编写
- 将
静态页面
文件 拷贝到项目的webapp
目录下:

- 将login.html内容修改成login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="/brand-demo/loginServlet" method="post" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">用户名或密码不正确</div>
<p>Username:<input id="username" name="username" type="text"></p>
<p>Password:<input id="password" name="password" type="password"></p>
<p>Remember:<input id="remember" name="remember" type="checkbox"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.html">没有账号?</a>
</div>
</form>
</div>
</body>
</html>
- 创建LoginServlet类
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
// 创建UserService实例,用于业务逻辑处理
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
// 2. 调用Service层进行登录验证
User user = service.login(username, password);
// 3. 根据验证结果进行分支处理
if(user != null) { // 用户验证成功
// 将用户对象存入Session(会话跟踪)
HttpSession session = request.getSession(); // 获取当前会话对象
session.setAttribute("user", user); // 存储用户对象到session
// 构建重定向路径(动态获取项目上下文路径)
String contextPath = request.getContextPath(); // 获取项目根路径
response.sendRedirect(contextPath + "/selectAllServlet"); // 重定向到品牌列表页面
} else { // 用户验证失败
// 存储错误信息到request
request.setAttribute("login_msg", "用户名或密码错误"); // 设置错误提示属性
// 转发回登录页面login.jsp(保留请求数据)
request.getRequestDispatcher("/login.jsp").forward(request, response); // 内部跳转到登录页
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
- 在brand.jsp中
<body>
标签内添加欢迎当前用户的提示信息:
<h1>${user.username},欢迎您</h1>
// user 对应LoginServlet中设置的session属性:session.setAttribute("user", user)
// username 是User对象的属性之一
- 修改login.jsp,将错误信息使用EL表达式来获取
修改前内容:<div id="errorMsg">用户名或密码不正确</div>
修改后内容: <div id="errorMsg">${login_msg}</div>
四、启动,访问测试
-
进入登录页面,输入错误的用户名或密码
-
输入正确的用户和密码信息
记住我-设置Cookie
1. 需求:
如果用户勾选“记住用户” ,则下次访问登陆页面 自动填充用户名密码。这样可以提升用户的体验。
2. 实现流程分析
实现用户登录时勾选"记住我"后,下次访问登录页自动填充用户名密码的功能。
关键问题:
- 持久化存储:用户信息需要在浏览器关闭后依然存在
- 自动填充:下次访问登录页时自动读取存储的信息
技术方案
使用 Cookie 存储登录凭证:
- 将用户名和密码写入 Cookie 中,并且持久化存储Cookie,下次访问浏览器会自动携带Cookie
- 在页面获取Cookie数据后,设置到用户名和密码框中
- 写入Cookie的条件:用户登陆成功;且在登录页面勾选了
记住我
的复选框

核心流程
- 在登录页添加复选框(如
<input type="checkbox" name="rememberMe" value="true"> 记住我
),用户勾选后,表单提交时多携带 remember 参数到后端。 LoginServlet
获取请求参数username
、password
、remember
后,调用 Service 层验证用户名和密码- 登录成功,并且用户在前端勾选了
记住我
,需要往Cookie中写入用户名和密码的数据,并设置Cookie存活时间 - 设置成功后,下次访问登录页时,通过 JavaScript 读取 Cookie 并填充表单。
3. 具体实现
- 在login.jsp为复选框设置值
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="/brand-demo/loginServlet" method="post" id="form"> <!-- 登录表单,提交到LoginServlet -->
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div> <!-- 错误信息显示区域(使用EL表达式) -->
<p>Username:<input id="username" name="username" type="text"></p> <!-- 用户名输入框 -->
<p>Password:<input id="password" name="password" type="password"></p> <!-- 密码输入框 -->
<p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p> <!-- "记住我"复选框 -->
<div id="subDiv">
<input type="submit" class="button" value="login up"> <!-- 提交按钮 -->
<input type="reset" class="button" value="reset"> <!-- 重置按钮 -->
<a href="register.html">没有账号?</a> <!-- 注册链接 -->
</div>
</form>
</div>
</body>
</html>
- 在LoginServlet获取复选框的值,并在登录成功后设置Cookie
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 获取请求参数
String username = request.getParameter("username"); // 从请求中获取用户名
String password = request.getParameter("password"); // 从请求中获取密码
String remember = request.getParameter("remember"); // 获取"记住我"复选框状态
// 2. 调用Service层进行登录验证
User user = service.login(username, password);
// 3. 验证结果处理
if(user != null){ // 登录成功
// 检查是否勾选"记住我",勾选了就发送Cookie
if("1".equals(remember)){ // 避免空指针,将常量放前面
// 创建用户名Cookie
Cookie c_username = new Cookie("username", username);
c_username.setMaxAge(60 * 60 * 24 * 7); // 设置有效期7天(秒)
// 创建密码Cookie
Cookie c_password = new Cookie("password", password);
c_password.setMaxAge(60 * 60 * 24 * 7); // 相同有效期
// 添加Cookie到响应
response.addCookie(c_username);
response.addCookie(c_password);
}
// 将登陆成功后的user对象,存储到session
HttpSession session = request.getSession(); // 获取当前会话
session.setAttribute("user", user); // 存储用户对象
// 重定向到主页
String contextPath = request.getContextPath(); // 获取项目根路径
response.sendRedirect(contextPath + "/selectAllServlet"); // 重定向到品牌列表页
} else { // 登录失败
// 存储错误信息到request
request.setAttribute("login_msg", "用户名或密码错误");
// 转发回登录页login.jsp
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response); // POST请求转给doGet处理
}
}
- 启动访问测试
只有当前用户名和密码输入正确,并且勾选了Remeber的复选框,在响应头中才可以看得cookie的相关数据
记住我-获取Cookie
1. 需求
获取Cookie中的数据,在登录页面自动填充之前保存的用户名和密码
2. 实现流程分析
技术方案
使用 EL 表达式 直接获取 Cookie 值:
- 语法:
${cookie.key.value}
- 机制:自动从请求携带的 Cookie 中查找指定键的值
在login.jsp用户名和密码的表单输入框,使用value值给表单元素添加默认值:
<input id="username" name="username" type="text" value="${cookie.username.value}"> <!-- 自动填充用户名 -->
<input id="password" name="password" type="password" value="${cookie.password.value}"> <!-- 自动填充密码 -->
3. 具体实现
- 修改login.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="/brand-demo/loginServlet" method="post" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div>
<p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
<p>Password:<input id="password" name="password" value="${cookie.password.value}" type="password"></p>
<p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.html">没有账号?</a>
</div>
</form>
</div>
</body>
</html>
4. 访问测试
重新访问登录页面,就可以看得用户和密码已经被填充。

用户注册功能
1. 需求
注册功能:将用户信息(用户名、密码)保存至数据库
验证码功能:
- 前端展示可点击切换的验证码图片
- 后端校验验证码正确性,错误则拒绝注册

2. 实现流程分析

(1) 前端通过表单发送请求和数据给Web层的RegisterServlet
(2) 在RegisterServlet中接收请求和数据(用户名、密码和用户输入的验证码)
(3) RegisterServlet接收到请求和数据后,调用Service层完成用户信息的保存
(4) 在Service层需要编写UserService类,在类中实现register方法,需要判断用户是否已经存在,如果不存在,则完成用户数据的保存
(5) 在UserMapper接口中,声明两个方法,一个是根据用户名查询用户信息方法,另一个是保存用户信息方法
(6) 在UserService类中保存成功则返回true,失败则返回false,将数据返回给Web层
(7) Web层获取到结果后,如果返回的是true,则提示注册成功
,并转发到登录页面,如果返回false则提示用户名已存在
并转发到注册页面
3. 具体实现
-
Dao层代码参考资料中的内容完成
-
编写Service层代码
public class UserService {
//1.使用工具类获取SqlSessionFactory对象
SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 用户注册业务方法
* @param user 包含用户名和密码的用户对象
* @return 注册成功返回true,用户名已存在返回false
*/
public boolean register(User user){
//2. 获取SqlSession对象(数据库会话)
SqlSession sqlSession = factory.openSession();
//3. 通过SqlSession获取UserMapper接口的代理实现
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//4. 根据用户名查询数据库是否存在相同用户
User u = mapper.selectByUsername(user.getUsername());
if(u == null) { // 当查询结果为null时表示用户名可用
// 执行用户数据插入操作,调用Mapper保存用户数据
mapper.add(user);
// 提交事务(确保数据持久化)
sqlSession.commit();
}
// 关闭数据库会话(释放连接资源)
sqlSession.close();
// 根据用户是否存在返回注册结果,u为null表示注册成功
return u == null;
}
}
- 完成页面和Web层的代码编写
3.1. 将register.html内容修改成register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>欢迎注册</title>
<link href="css/register.css" rel="stylesheet">
</head>
<body>
<div class="form-div">
<div class="reg-content">
<h1>欢迎注册</h1>
<span>已有帐号?</span> <a href="login.html">登录</a>
</div>
<form id="reg-form" action="/brand-demo/registerServlet" method="post">
<table>
<tr>
<td>用户名</td>
<td class="inputs">
<input name="username" type="text" id="username">
<br>
<span id="username_err" class="err_msg" style="display:none">用户名不太受欢迎</span>
</td>
</tr>
<tr>
<td>密码</td>
<td class="inputs">
<input name="password" type="password" id="password">
<br>
<span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
</td>
</tr>
<tr>
<td>验证码</td>
<td class="inputs">
<input name="checkCode" type="text" id="checkCode">
<img src="imgs/a.jpg">
<a href="#" id="changeImg" >看不清?</a>
</td>
</tr>
</table>
<div class="buttons">
<input value="注 册" type="submit" id="reg_btn">
</div>
<br class="clear">
</form>
</div>
</body>
</html>
3.2. 编写RegisterServlet
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
// 创建UserService业务逻辑对象
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取请求参数
String username = request.getParameter("username"); // 从请求获取用户名
String password = request.getParameter("password"); // 从请求获取密码
// 封装用户对象
User user = new User();
user.setUsername(username); // 设置用户名属性
user.setPassword(password); // 设置密码属性
//2. 调用service 注册
boolean flag = service.register(user);
//3. 判断注册成功与否
if(flag){ // 注册成功
// 设置成功消息属性
request.setAttribute("register_msg","注册成功,请登录");
// 转发到登录页面
request.getRequestDispatcher("/login.jsp").forward(request,response);
}else { // 注册失败
// 设置错误消息属性
request.setAttribute("register_msg","用户名已存在");
// 转发回注册页面
request.getRequestDispatcher("/register.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
3.3. 需要在页面上展示后台返回的错误信息,需要修改 register.jsp
修改前:<span id="username_err" class="err_msg" style="display:none">用户名不太受欢迎</span>
修改后:<span id="username_err" class="err_msg">${register_msg}</span>
3.4. 如果注册成功,需要把成功信息展示在登录页面,所以也需要修改 login.jsp
修改前:<div id="errorMsg">${login_msg}</div>
修改后:<div id="errorMsg">${login_msg} ${register_msg}</div>
3.5. 修改login.jsp,将注册跳转地址修改为register.jsp
修改前:<a href="register.html">没有账号?</a>
修改后: <a href="register.jsp">没有账号?</a>
3.6. 启动测试,
如果是注册的用户信息已经存在:\
如果注册的用户信息不存在,注册成功:

验证码-展示
1. 需求分析
展示验证码:展示验证码图片,并可以点击切换\
验证码的生成是通过工具类来实现的,具体的工具类参考
04-资料\1. 登录注册案例\CheckCodeUtil.java
在该工具类中编写main方法进行测试:
public static void main(String[] args) throws IOException {
// 创建文件输出流,指定验证码图片保存路径
OutputStream fos = new FileOutputStream("d://a.jpg"); // 输出到D盘根目录的a.jpg文件
// 调用验证码工具生成图片并获取验证码文本
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, fos, 4);
// 参数说明:100=图片宽度, 50=图片高度, fos=输出流, 4=验证码字符数
// 关闭文件输出流,释放系统资源
fos.close();
// 在控制台打印输出验证码(用于调试)
System.out.println(checkCode);
}
生成完验证码以后,我们就可以知晓:
- 验证码就是使用Java代码生成的一张图片
- 验证码的作用:防止机器自动注册,攻击服务器
2. 实现流程分析

- 前端发送请求给CheckCodeServlet
- CheckCodeServlet接收到请求后,生成验证码图片,将图片用Reponse对象的输出流写回到前端
将图片写回到前端浏览器的流程
- Java中已经有工具类生成验证码图片,测试类中只是把图片生成到磁盘上
- 替换输出流对象:通过
getOutputStream()
方法将原本写入磁盘文件的OutputStream
替换为ServletResponse
的字节输出流:ServletOutputStream sos = response.getOutputStream(); ImageIO.write(bufferedImage, "jpg", sos); // 直接写入响应流
- 设置响应头:必须明确指定
Content-Type
为图片格式(如image/jpeg
),并禁用缓存:response.setContentType("image/jpeg"); response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // 防止缓存[13]()
- 验证码存储与校验
将生成的验证码字符串存入HttpSession
,后续通过session.getAttribute()
校验用户输入。
3. 具体实现
- 修改 Register.jsp 页面,将验证码的图片从后台获取
<tr>
<td>验证码</td>
<td class="inputs">
<input name="checkCode" type="text" id="checkCode"> <!-- 验证码输入框 -->
<img id="checkCodeImg" src="/brand-demo/checkCodeServlet"> <!-- 验证码图片,初始请求Servlet获取图片 -->
<a href="#" id="changeImg">看不清?</a> <!-- 刷新验证码的链接 -->
</td>
</tr>
<script>
// 为刷新链接绑定点击事件
document.getElementById("changeImg").onclick = function () {
// 修改验证码图片的src:添加时间戳参数防止浏览器缓存
document.getElementById("checkCodeImg").src = "/brand-demo/checkCodeServlet?" + new Date().getMilliseconds();
}
</script>
代码拆解:
document.getElementById("checkCodeImg")
: 获取页面中ID为checkCodeImg
的图片元素(即验证码图片).src = ...
:修改图片元素的src
属性(图片源地址)"/brand-demo/checkCodeServlet"
:服务器端生成验证码图片的接口地址+ new Date().getMilliseconds()
:在URL后添加当前时间的毫秒值(0-999之间的随机数)
- 编写CheckCodeServlet类,用来接收请求生成验证码
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 生成验证码
ServletOutputStream os = response.getOutputStream(); // 获取字节输出流
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4); // 生成验证码并写入流
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
outputVerifyImage(100, 50, os, 4);
:100:验证码图片宽度(像素);
50:验证码图片高度(像素);os:字节输出流,用于将生成的图片数据写入HTTP响应;4:验证码字符长度。
验证码-校验
1. 需求分析
- 判断程序生成的验证码 和 用户输入的验证码 是否一致,若不一致则阻止注册流程。
- 由于验证码图片访问和提交注册表单是两次独立请求,为了在这两次请求间共享验证码数据,需将程序生成的验证码存入 Session 中。
2. 实现流程分析

- 生成验证码并存储:在
CheckCodeServlet
中生成验证码,同时将验证码数据存入 Session 对象。 - 前端数据提交:前端将验证码和注册所需数据一并提交到后台,由
RegisterServlet
类接收处理。 - 验证码校验:
RegisterServlet
类接收到请求和数据后,从其中提取用户输入的验证码,并与 Session 中存储的验证码进行比对。 - 注册结果处理:
- 若两者一致,完成注册流程。
- 若不一致,向用户提示错误信息,阻止注册。
3. 具体实现
- 修改CheckCodeServlet类,将验证码存入Session对象
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 生成验证码
ServletOutputStream os = response.getOutputStream();
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
// 存入Session
HttpSession session = request.getSession();
session.setAttribute("checkCodeGen",checkCode);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
- 在RegisterServlet中,获取页面的和session对象中的验证码,进行对比
package com.itheima.web;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取用户名和密码数据
String username = request.getParameter("username");
String password = request.getParameter("password");
// 创建一个 User 对象,将获取到的用户名设置到其中
User user = new User();
user.setUsername(username);
user.setPassword(password);
// 从请求参数中获取用户输入的验证码
String checkCode = request.getParameter("checkCode");
// 从Session获取验证码
HttpSession session = request.getSession(); // 获取当前请求对应的 HttpSession 对象
String checkCodeGen = (String) session.getAttribute("checkCodeGen"); // 从 Session 中获取之前存储的验证码
// 比对
if(!checkCodeGen.equalsIgnoreCase(checkCode)){ // 使用 equalsIgnoreCase 方法忽略大小写比较用户输入的验证码和 Session 中的验证码
// 如果验证码不匹配,则将错误信息存入请求属性中,并转发请求到注册页面,显示错误信息
request.setAttribute("register_msg","验证码错误");
request.getRequestDispatcher("/register.jsp").forward(request,response);
return; // 不允许注册,直接返回,终止后续注册流程
}
// 调用 UserService 的 register 方法进行用户注册,并获取注册结果
boolean flag = service.register(user);
// 判断注册成功与否
if(flag){
// 注册成功,显示成功信息,并跳转到登陆页面
request.setAttribute("register_msg","注册成功,请登录");
request.getRequestDispatcher("/login.jsp").forward(request,response);
}else {
//注册失败,显示失败信息,并跳转到注册页面
request.setAttribute("register_msg","用户名已存在");
request.getRequestDispatcher("/register.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
至此,用户的注册登录功能就已经完成了。