JavaWeb(3-会话技术)

会话技术

会话跟踪技术概述

会话:是指浏览器和服务器之间的一段通信过程。从用户访问网站开始,到关闭浏览器或者任一方断开连接为止,称为一次会话。在一次会话中,浏览器和服务器之间可以多次发送请求和响应。

  • 建立过程: 当浏览器向服务器发送请求,并收到响应后,会话就建立了。
  • 持续时间: 只要浏览器和服务器没有断开连接,会话就持续存在。
  • 多次交互: 在同一会话中,可以进行多次请求与响应。

会话跟踪:是一种技术,用来维护会话中的数据状态。服务器需要识别多个请求是否来自同一个用户,并在这些请求之间共享数据。

  • 当一个用户在同一会话中多次发送请求时,服务器可以通过会话跟踪识别这些请求来自同一个用户。
  • 识别后,服务器能够在多次请求之间共享数据,从而实现复杂功能。
  • 场景举例:
    • 购物车功能:
      第一次请求:将商品加入购物车。
      第二次请求:进入购物车页面。
      要显示加入的商品(即后一次请求要想展示前一次请求所添加的商品),就需要共享数据。
    • 用户登录信息展示:
      用户登录后,每次请求都会显示用户名(例如京东或百度)。
    • 登录时的“记住我”功能:
      勾选“记住我”后,下次登录时会自动填充用户名和密码。
    • 图形验证码功能:
      第一次请求生成图形验证码。
      第二次请求提交图形验证码。
      两次请求之间需要对验证码进行比对。

为什么浏览器和服务器不能直接共享数据?

因为HTTP协议是无状态的

  • 每次浏览器向服务器发送请求时,服务器都将其视为全新的请求。
  • 无状态设计的优点是每个请求独立互不影响,但缺点是无法实现请求之间的数据共享。

会话跟踪的实现方式

为了实现会话跟踪,有两种主要技术:

  • 客户端会话跟踪技术:Cookie,数据存储在 浏览器端
  • 服务端会话跟踪技术:Session,数据存储在 服务器端

Cookie 技术详解

Cookie: 是一种客户端会话跟踪技术,将数据存储在客户端(浏览器)。
在后续的请求中,浏览器会携带这些数据访问服务器,从而实现数据共享。

Cookie 的工作流程

  1. 浏览器向服务端发送请求。
  2. 服务端接收请求并生成一个 Cookie 对象,将数据存入其中。
  3. 服务端通过 HTTP 响应头将 Cookie 发送到浏览器。
  4. 浏览器接收 Cookie 并将其存储在内存或硬盘中(此时浏览器和服务端就建立了一次会话)。
  5. 在后续请求中,浏览器再次发送HTTP请求给服务端时,会自动将 Cookie 对象中的所有数据携带到服务器。
  6. 服务器通过这些 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对象并存入数据,然后将数据发送给浏览器

具体实现步骤:

  1. 创建Maven项目,项目名称为cookie-demo,并在pom.xml添加依赖
  2. 编写Servlet类,名称为 AServlet
  3. 在AServlet中创建Cookie对象,存入数据,发送给前端
  4. 启动测试,在浏览器查看Cookie对象中的值

具体实现代码:

  1. 创建编写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);
        }
    }
    
  2. 访问 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 对象中的数据

具体实现步骤:

  1. 编写一个新Servlet类,名称为 BServlet
  2. 在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值
  3. 启动测试,在控制台打印出获取的值

具体实现代码:

  1. 创建编写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);
        }
    }
    
  2. 访问 BServlet,在控制台查看打印的 Cookie 值。
Cookie 的原理分析

Cookie 的底层实现基于 HTTP 协议,涉及以下两个请求头:

  1. 响应头:Set-Cookie
    • 服务器通过 Set-Cookie 响应头发送 Cookie 数据。
  2. 请求头:Cookie
    • 浏览器在后续请求中,通过 Cookie 请求头携带数据。

详析

  1. AServlet 设置并发送 Cookie:当后端的 AServlet 需要向前端发送 Cookie 时,它通过 Set-Cookie 将数据(如 Set-Cookie: username=zs)传递给浏览器。

  2. 浏览器接收并存储 Cookie:浏览器收到响应后,会从响应头中提取 Set-Cookie 的值,并将其存储在浏览器内存或本地(根据 Cookie 的配置)。

  3. 浏览器发送 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)进行 查看响应头中的数据 1629393428733.png
  • 访问BServlet对应的地址 http://localhost:8080/应用路径/bServlet ,使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行 查看请求头中的数据 1629393578667.png
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的相关信息。 1629424844041.png
Cookie 存储中文

默认情况下,Cookie 不支持直接存储中文字符。存储中文时需要使用 URL 编码解码

  • 存储时编码: 使用 URLEncoder.encode() 将中文编码为 URL 格式。
  • 读取时解码: 使用 URLDecoder.decode() 将编码值解码为中文。

案例:存储和获取中文 Cookie

  1. 在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); 
        } 
    }
    
  2. )在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 的工作流程

  1. 用户第一次访问服务器后,服务器会创建一个新的 Session 对象并存储数据。
  2. 新 Session 创建时,服务器会生成一个唯一标识 (Session ID),并通过 HTTP 响应头将其发送给浏览器,存储在浏览器的 Cookie 中(默认名为 JSESSIONID)。
  3. 浏览器在后续请求中自动携带该 Cookie 信息,使服务器能找到对应的 Session 对象,实现数据共享。
Session 的基本使用

核心方法

  1. 获取 Session 对象
    HttpSession session = request.getSession();
    
  2. 存储数据到 session 域中
    session.setAttribute("key", value);
    
  3. 根据 key,获取数据
    Object value = session.getAttribute("key");
    
  4. 根据 key,删除数据
    session.removeAttribute("key");
    

案例:在 Servlet 中存取数据

需求:在一个 Servlet 中存储数据,在另一个 Servlet 中获取数据。

具体实现步骤:

  1. 编写第一个Servlet类,名称为 SessionDemo1,用于存储数据到Session中。
  2. 在SessionDemo1的doGet或doPost方法中,通过request.getSession()方法获取Session对象。
  3. 使用Session对象的setAttribute(String name, Object value)方法存储数据。
  4. 编写第二个Servlet类,名称为 SessionDemo2,用于从Session中获取数据。
  5. 在SessionDemo2的doGet或doPost方法中,同样通过request.getSession()方法获取Session对象。
  6. 使用Session对象的getAttribute(String name)方法获取存储的数据。
  7. 在SessionDemo2中,将获取到的值打印到控制台或进行其他处理。
  8. 配置web.xml文件,映射SessionDemo1和SessionDemo2的URL路径。
  9. 启动Web服务器,访问SessionDemo1的URL,使其设置Session数据。
  10. 访问SessionDemo2的URL,观察控制台输出或处理结果。

具体实现代码:

  1. 创建 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");      // 存储数据
        }
    }
    
  2. 创建 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);
        }
    }
    
  3. 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);
        }
    }
    
  4. 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);
        }
    }
    
  5. 测试

    1. 先访问 http://localhost:8080/cookie-demo/demo1 将数据存入 Session。
    2. 再访问 http://localhost:8080/cookie-demo/demo2 从 Session 中获取数据并输出到控制台。
    3. 查看控制台 image.png

注意:Session中可以存储的是一个Object类型的数据,也就是说Session中可以存储任意数据类型。

Session 的原理分析

Session 实现数据共享原理

Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个

Session是基于Cookie来实现

详细流程 image.png

  1. 首次请求创建Session
    • 用户访问demo1时,Tomcat生成Session对象并分配唯一ID(如id:10)。
    • Session数据(如用户信息)存储于服务器内存或数据库中。
  2. 响应中添加Session ID
    • Tomcat在响应头中添加Set-Cookie: JSESSIONID=10,告知浏览器保存此ID。
    • 示例:Chrome开发者工具的“网络”标签中可查看此响应头。
  3. 浏览器存储Cookie
    • 浏览器将JSESSIONID=10存入内存(未设置过期时间时),并在同一会话的后续请求中自动附加此Cookie。
  4. 后续请求身份识别
    • 访问demo2时,请求头携带Cookie: JSESSIONID=10,Tomcat据此查找对应Session。
    • 若找到id:10的Session,直接复用数据;否则新建Session(如ID失效或首次访问)。
  5. 会话终止与重建
    • 关闭浏览器:内存中的Cookie被清除,后续请求无JSESSIONID,服务器创建新Session。
    • 超时机制:Session默认有效期(如30分钟),无活动则自动销毁。

demo1demo2代表服务器上的两个不同资源(如Servlet、API接口或页面)

查看响应头Set-Cookie图示
(1)使用chrome浏览器访问http://localhost:8080/cookie-demo/demo1 ,打开开发者模式(F12或Ctrl+Shift+I),查看响应头数据: image.png

(2)使用chrome浏览器再次访问http://localhost:8080/cookie-demo/demo2 ,查看请求头数据: image.png

Session 的细节

只要服务器是正常关闭和启动,session中的数据是可以被保存下来的。

Session 的钝化与活化
  • 钝化:服务器正常关闭时,Tomcat会自动将Session 数据会被保存到硬盘的文件中 (SESSIONS.ser)。
    钝化的数据路径为: 项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser

    image.png
  • 活化:服务器正常重启时,从文件中将 Session 数据加载到Session中。
    数据加载到Session中后,路径中的SESSIONS.ser文件会被删除掉

注意:这里所说的关闭和启动应该要确保是正常的关闭和启动。

正常关闭Tomcat服务器方式之一: 使用命令行的方式来启动和停止Tomcat服务器

启动:进入到项目pom.xml所在目录,执行 tomcat7:run image.png

停止:在启动的命令行界面,键入Ctrl+C image.png

补充:

  • 上述方法仅适用于开发/测试环境
  • 启动命令 tomcat7:run 需 依赖环境: 该命令属于 Maven的Tomcat7插件tomcat7-maven-plugin),项目中需在pom.xml声明插件依赖

测试流程

  1. 先启动Tomcat服务器
  2. 访问http://localhost:8080/cookie-demo/demo1 ,将数据存入session中
  3. 正确停止Tomcat服务器
  4. 再次重新启动Tomcat服务器
  5. 访问http://localhost:8080/cookie-demo/demo2 查看是否能获取到session中的数据

经过测试,会发现只要服务器是正常关闭和启动,session中的数据是可以被保存下来的。

Session数据 与 Session ID 及 Cookie 之间关系

[首次请求] → [服务端生成 Session 和 Session ID] → [通过 Cookie 返回 Session ID 给客户端并储存]
[后续请求] → [客户端发送 Cookie 携带 Session ID] → [服务端读取对应 Session 数据]

一、协作机制

  1. 用户的多个数据Session 依赖 Cookie 关联(默认)
  • Session ID 传递:服务端生成 Session 数据及唯一 ID,通过 Set-Cookie 返回 ID 给客户端。
  • 客户端存储:浏览器将 Session ID 保存在 Cookie(如 JSESSIONID),后续请求自动携带。
  • 服务端关联:服务端通过 Session ID 找到对应的 Session 数据。
  1. 角色分工
    • 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 钝化与活化

  1. 触发条件

    • 钝化:仅在 Tomcat 正常关闭时,内存中的 Session 会被序列化到磁盘(work 目录下的 .ser 文件)。
    • 活化:Tomcat 重启后,自动从磁盘加载 Session 数据到内存。
  2. 注意事项

    • 非持久化场景:若服务器异常崩溃或强制终止,Session 数据仍会丢失。
    • 生产环境限制
      • 仅适用于单机部署,不适用于分布式集群(Session 无法跨节点共享)。
      • 默认存储机制效率低,建议替换为 Redis 等外部存储。

补充说明

  1. 安全性权衡

    • 长期保存 Session ID 可能引发 Session 固定攻击(Session Fixation)。建议:
      • 用户登录后生成新的 Session ID。
      • 绑定用户 IP/User-Agent 等指纹信息。
      • 设置较短的 Session 超时时间,结合 Refresh Token 机制。
      • 优先通过 Token 机制(如 JWT)实现长期会话,而非过度依赖延长 Session 生命周期。
  2. 浏览器隐私模式的影响

    • 隐私模式(无痕浏览)下,浏览器关闭后所有 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中,无需修改)。 image.png

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中获取数据,访问网页如图所示\ image.png

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。

对比总结

特性CookieSession
存储位置客户端(浏览器)服务端(服务器内存或数据库)
安全性低(数据明文存储)高(仅存储Session ID在客户端)
数据大小限制单个Cookie ≤ 4KB,域名下最多20个左右无限制(依赖服务器资源)
生命周期可设置setMaxAge()长期保留默认30分钟(可配置),浏览器关闭失效
性能影响不占用服务器资源占用服务器内存或存储空间

应用场景

  1. Cookie的典型场景

    • 记住我功能:长期保存用户登录状态(如“下次自动登录”)。
    • 用户偏好设置:存储语言、主题等个性化配置。
    • 未登录购物车:临时保存用户未登录时的购物车信息。
  2. Session的典型场景

    • 用户登录信息:存储用户ID、角色权限等敏感数据。
    • 验证码校验:服务端保存生成的验证码,与用户输入对比。
    • 临时会话数据:如多步骤表单填写过程中的中间数据。

如何选择?

  • 用Cookie:需长期保存的非敏感数据(如用户偏好)。
  • 用Session:短期且敏感的数据(如登录状态)。
  • 结合使用:例如用Cookie保存用户昵称(非敏感),用Session保存用户ID(敏感)。两者结合可构建安全高效的会话管理机制。实际开发中需根据业务需求权衡选择。

示例代码片段

  1. 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();
    }
}
  1. Session操作(Java)
// 存储数据
HttpSession session = request.getSession();
session.setAttribute("user", loggedInUser);

// 读取数据
User user = (User) session.getAttribute("user");

// 销毁Session
session.invalidate(); // 用户退出时调用

用户登录注册案例

需求分析
  • 成功登录:跳转列表页,显示当前用户名。
  • 失败登录:返回登录页,显示错误信息。
  • 记住用户:勾选后,下次访问登录页面自动填充用户名密码(Cookie实现)。
  • 完成注册功能,并实现验证码功能
image.png image.png

技术栈

  • 前端:HTML + JSP
  • 后端:Servlet + Service + Dao
  • 数据库:MySQL + MyBatis
用户登录功能

流程总览
前端表单 → Web层(Servlet) → Service层 → Dao层(Mapper) → 数据库
数据逐层传递,结果逐层返回,最终由Web层处理响应。

分步解析

  1. 前端提交请求:用户填写表单、点击登录后,表单通过HTTP请求将数据(usernamepassword)提交至后端LoginServlet
  2. Web层接收数据:以LoginServlet为入口接收请求参数,从中提取用户名和密码,然后调用Service层处理业务逻辑。
  3. Service层处理业务逻辑:由UserService类负责协调数据与业务规则;实现login()方法,调用Dao层查询数据库,仅进行逻辑调度,不直接操作数据库。
  4. Dao层访问数据库UserMapper接口定义数据库操作,声明SQL查询方法(如selectByUsernameAndPassword),将查询结果封装为User对象返回给Service层。
  5. 数据逐层返回:数据按 “数据库 → Dao层 → Service层 → Web层” 的路径逐层返回。查询成功时返回包含用户数据的User对象,查询失败时返回null
  6. Web层处理响应
  • 成功登录:将User对象存入Session(维持登录状态),重定向至列表页,页面显示用户名(从Session读取)。
  • 登录失败:在请求中存入错误信息(如“用户名或密码错误”),转发回登录页,前端显示错误提示。

流程图 image.png

具体实现

一、 完成Dao层的代码编写

  1. 在 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);
}
  1. 在 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 + '\'' +
                '}';
    }
}
  1. 位于 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层的代码编写

  1. 静态页面文件 拷贝到项目的webapp目录下:
image.png
  1. 将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">&nbsp;&nbsp;&nbsp;
            <a href="register.html">没有账号?</a>
        </div>
    </form>
</div>
</body>
</html>
  1. 创建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);
    }
}
  1. 在brand.jsp中 <body> 标签内添加欢迎当前用户的提示信息:
<h1>${user.username},欢迎您</h1>
// user 对应LoginServlet中设置的session属性:session.setAttribute("user", user)  
// username 是User对象的属性之一
  1. 修改login.jsp,将错误信息使用EL表达式来获取
修改前内容:<div id="errorMsg">用户名或密码不正确</div>
修改后内容: <div id="errorMsg">${login_msg}</div>

四、启动,访问测试

  1. 进入登录页面,输入错误的用户名或密码 image.png

  2. 输入正确的用户和密码信息 image.png

记住我-设置Cookie

1. 需求:

如果用户勾选“记住用户” ,则下次访问登陆页面 自动填充用户名密码。这样可以提升用户的体验。 image.png

2. 实现流程分析

实现用户登录时勾选"记住我"后,下次访问登录页自动填充用户名密码的功能。

关键问题

  • 持久化存储:用户信息需要在浏览器关闭后依然存在
  • 自动填充:下次访问登录页时自动读取存储的信息

技术方案

使用 Cookie 存储登录凭证:

  • 将用户名和密码写入 Cookie 中,并且持久化存储Cookie,下次访问浏览器会自动携带Cookie
  • 在页面获取Cookie数据后,设置到用户名和密码框中
  • 写入Cookie的条件:用户登陆成功;且在登录页面勾选了记住我的复选框
image.png

核心流程

  1. 在登录页添加复选框(如 <input type="checkbox" name="rememberMe" value="true"> 记住我),用户勾选后,表单提交时多携带 remember 参数到后端。
  2. LoginServlet 获取请求参数usernamepasswordremember后,调用 Service 层验证用户名和密码
  3. 登录成功,并且用户在前端勾选了记住我,需要往Cookie中写入用户名和密码的数据,并设置Cookie存活时间
  4. 设置成功后,下次访问登录页时,通过 JavaScript 读取 Cookie 并填充表单。

3. 具体实现

  1. 在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">  <!-- 重置按钮 -->
            &nbsp;&nbsp;&nbsp;
            <a href="register.html">没有账号?</a>  <!-- 注册链接 -->
        </div>
    </form>
</div>
</body>
</html>
  1. 在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处理
    }
}
  1. 启动访问测试

只有当前用户名和密码输入正确,并且勾选了Remeber的复选框,在响应头中才可以看得cookie的相关数据 image.png

记住我-获取Cookie

1. 需求

获取Cookie中的数据,在登录页面自动填充之前保存的用户名和密码 image.png

2. 实现流程分析

技术方案
使用 EL 表达式 直接获取 Cookie 值:

  • 语法:${cookie.key.value}
  • 机制:自动从请求携带的 Cookie 中查找指定键的值 image.png

在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. 具体实现

  1. 修改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">&nbsp;&nbsp;&nbsp;
            <a href="register.html">没有账号?</a>
        </div>
    </form>
</div>
</body>
</html>

4. 访问测试

重新访问登录页面,就可以看得用户和密码已经被填充。

image.png
用户注册功能

1. 需求

注册功能:将用户信息(用户名、密码)保存至数据库
验证码功能

  • 前端展示可点击切换的验证码图片
  • 后端校验验证码正确性,错误则拒绝注册
image.png

2. 实现流程分析

image.png

(1) 前端通过表单发送请求和数据给Web层的RegisterServlet
(2) 在RegisterServlet中接收请求和数据(用户名、密码和用户输入的验证码)
(3) RegisterServlet接收到请求和数据后,调用Service层完成用户信息的保存
(4) 在Service层需要编写UserService类,在类中实现register方法,需要判断用户是否已经存在,如果不存在,则完成用户数据的保存
(5) 在UserMapper接口中,声明两个方法,一个是根据用户名查询用户信息方法,另一个是保存用户信息方法
(6) 在UserService类中保存成功则返回true,失败则返回false,将数据返回给Web层
(7) Web层获取到结果后,如果返回的是true,则提示注册成功,并转发到登录页面,如果返回false则提示用户名已存在并转发到注册页面

3. 具体实现

  1. Dao层代码参考资料中的内容完成

  2. 编写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;
    }
}
  1. 完成页面和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. 启动测试,

如果是注册的用户信息已经存在:\ image.png

如果注册的用户信息不存在,注册成功:

image.png
验证码-展示

1. 需求分析

展示验证码:展示验证码图片,并可以点击切换\ image.png

验证码的生成是通过工具类来实现的,具体的工具类参考

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. 实现流程分析

image.png
  1. 前端发送请求给CheckCodeServlet
  2. CheckCodeServlet接收到请求后,生成验证码图片,将图片用Reponse对象的输出流写回到前端

将图片写回到前端浏览器的流程

  1. Java中已经有工具类生成验证码图片,测试类中只是把图片生成到磁盘上
  2. 替换输出流对象:通过getOutputStream()方法将原本写入磁盘文件的OutputStream替换为ServletResponse的字节输出流:
    ServletOutputStream sos = response.getOutputStream(); 
    ImageIO.write(bufferedImage,  "jpg", sos); // 直接写入响应流 
    
  3. 设置响应头:必须明确指定Content-Type为图片格式(如image/jpeg),并禁用缓存:
    response.setContentType("image/jpeg"); 
    response.setHeader("Cache-Control",  "no-cache, no-store, must-revalidate"); // 防止缓存[13]()
    
  4. 验证码存储与校验
    将生成的验证码字符串存入HttpSession,后续通过session.getAttribute() 校验用户输入。

3. 具体实现

  1. 修改 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之间的随机数)
  1. 编写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. 实现流程分析

image.png
  1. 生成验证码并存储:在 CheckCodeServlet 中生成验证码,同时将验证码数据存入 Session 对象。
  2. 前端数据提交:前端将验证码和注册所需数据一并提交到后台,由 RegisterServlet 类接收处理。
  3. 验证码校验:RegisterServlet 类接收到请求和数据后,从其中提取用户输入的验证码,并与 Session 中存储的验证码进行比对。
  4. 注册结果处理:
  • 若两者一致,完成注册流程。
  • 若不一致,向用户提示错误信息,阻止注册。

3. 具体实现

  1. 修改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);
    }
}
  1. 在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);
    }
}

至此,用户的注册登录功能就已经完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值