前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >26. 会话技术-Session的使用

26. 会话技术-Session的使用

作者头像
Devops海洋的渔夫
发布2021-11-19 16:11:38
1K0
发布2021-11-19 16:11:38
举报
文章被收录于专栏:Devops专栏

26. 会话技术-Session的使用

一、 Session

1.1 概述

session是服务器端的会话技术

代码语言:javascript
复制
# session的作用
 在一次会话的多次请求之间共享数据,将数据保存到服务器端

# HttpSession是一个域对象 
 HttpSession是一个接口
 域对象可以看成是map(存储多个键值对), cookie是一个entry(只能存一个键值对)
1. 域对象的方法 
 a. 存储数据
   void setAttribute(String name,Object value)
 b. 获取数据
   Object getAttribute(String name)
 c. 删除数据
   void removeAttribute(String name)
   
2. 生命周期: 一次会话的多次请求之间
  pageContext(JSP) < request < session < servletContext
   从api上来说, 小域对象可以获取大域对象 

1.2 工作原理

Session基于Cookie技术实现

下面来使用一个上医院看病的示例,来简单理解 Session

1591321065800

方法介绍

代码语言:javascript
复制
1. 获取session对象: 
  HttpSession  session = request.getSession()
  1). 通过请求对象创建一个会话对象,如果当前用户会话不存在,创建会话。  
  2). 如果会话已经存在,这个方法返回已经存在的会话对象。
2. 获取session的id
  String sessionId = session.getId();
3.  使当前session失效
  ression.invalidate(); 

“记得把项目中的index.jsp干掉(影响到 JSESSIONID的cookie) ”

编写示例代码

index.html: 编写一个页面,模拟两个请求
  • 第一次请求至服务端:第一次去医院,生成一个session,并且保存信息
  • 第二次请求至服务端:再次去医院,查看之前保存的session信息。查询session id 是否与 上一次保存的 session ID 一致

image-20210217165529664

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="Session01Servlet">第一次访问, 创建Session</a> <br>
    <a href="Session02Servlet">第二次访问, 查看Session信息</a>
</body>
</html>
Session01Servlet:创建Session

image-20210217165651306

代码语言:javascript
复制
package com.lijw;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @author Aron.li
 * @date 2021/2/17 16:56
 */
@WebServlet("/Session01Servlet")
public class Session01Servlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. (挂号) 获取session对象
        HttpSession session = request.getSession();
        //2. (看病) 存数据
        session.setAttribute("sick","有点虚");

        //3. tomcat自动实现,将sessionId通过cookie返回给浏览器
//        String id = session.getId();
//        Cookie cookie = new Cookie("JSESSIONID", id);
//        response.addCookie(cookie);
    }

}
Session02Servlet:读取Session信息

image-20210217165806174

代码语言:javascript
复制
package com.lijw;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @author Aron.li
 * @date 2021/2/17 16:57
 */
@WebServlet("/Session02Servlet")
public class Session02Servlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. (挂号) 获取session对象
        HttpSession session = request.getSession();
        Object sick = session.getAttribute("sick");
        System.out.println("value:" + sick + ",id:"  + session.getId());
    }
}
测试
  • 在 index.html 执行第一次请求,创建 session

image-20210217165909891

image-20210217170010048

  • 在 index.html 执行第二次信息,查看 session 的 ID

image-20210217170111939

image-20210217170137124

可以通过获取的 session ID 信息,我们可以知道 session 是同一个的。

1.3 Session细节

代码语言:javascript
复制
# 找不到当前会话中的session原因分析 (通俗版)
 问题: 什么时候找不到班长原来的病历本?
1. 班长的原因
 0). 只要班长把病例本编号弄丢了 (id弄丢了)
 1). 用户清除cookie(清除浏览记录)
 2). 用户关闭浏览器, 保存id的cookie默认会话级别,自动销毁了
   -> 通过持久化cookie,达到session持久化 (下一个)
2. 医院的原因
 0). 医院把病历本弄丢了
 1). 手动销毁session
  session.invalidate();
 2). 自动销毁session
     默认30分钟内不访问,自动销毁 (可以改)
     a. tomcat/conf/web.xml 默认设置 
      (对发布在此tomcat上所有项目生效)
     b. 在当前项目中修改 web.xml (会覆盖tomcat默认设置)
        <session-config>
                <session-timeout>30</session-timeout>
            </session-config>
    3). 服务器非正常关闭
     突然断电, 数据来不及保存
     正常关闭: session数据会会从内存保存硬盘上
      -> session 钝化和活化
代码语言:javascript
复制
# 找不到当前会话中的session原因分析(专业版)
1. 浏览器方面的原因
        0). 核心: 保存JSESSIONID的cookie被销毁
        1). 因为cookie存活时间默认为会话,所以用户关闭浏览器就会销毁(用户无意识)
          -> session持久化
        2). 用户清除浏览记录(包含cookie) 
        
2. 服务器方面的原因
  0). 核心: session对象是存在服务器的内存,被销毁
  1). session手动销毁:session.invalidate();
   备注: session对象立即销毁
   
  2). 过期销毁:session默认存活时间--30min
    备注: (该用户连续30分钟不访问,服务器会自动销毁session)
    文件配置: web.xml (tomcat中的默认设置)
    1. tomcat/config/web.xml 中有session-config配置 (全局有效)
    2. 我们可以在项目中web.xml覆盖其配置 (只对当前项目有效)
    <session-config>
                    <session-timeout>30</session-timeout>
                 </session-config>
                 
  3). 非正常关闭tomcat(比如突然断电)  
  备注: 如果正常关闭tomcat,tomcat在停止之前会钝化session,下次启动时活化

1.4 session的持久化

代码语言:javascript
复制
#浏览器关闭后,session的持久化方案 
1. 问题: 从以上的分析我们得知, 浏览器关闭之后,就找不到原来的session了
2. 原因:
        1. 浏览器关闭,服务器中的session是在的
        2. 但是前端的JESSIONID这个cookie消失了
  3. 浏览器提交请求没有这个id,服务器自然就找不到之前的session了

3. 解决: 浏览器关闭,session依然找到的
        1. 在Servlet中手动创建name=JESSIONID的cookie;
        2. 这个cookie存储session的id,设置持久化级别 setMaxAge(秒);
        3. 将JESSIONID的cookie响应给浏览器; 
代码语言:javascript
复制
@WebServlet("/Session01Servlet")
public class Session01Servlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //1. (挂号) 获取session对象
        HttpSession session = request.getSession();
        //2. (看病) 存数据
        session.setAttribute("sick","有点虚");

        //3. tomcat自动实现,将sessionId通过cookie返回给浏览器
//        String id = session.getId();
//        Cookie cookie = new Cookie("JSESSIONID", id);
//        response.addCookie(cookie);


        //4. 我们手动实现(设置持久级别),覆盖tomcat的自动实现
        String id = session.getId();
        Cookie cookie = new Cookie("JSESSIONID", id);
        cookie.setMaxAge(60*30); //30分钟
        response.addCookie(cookie);
    }

}

1.5 session的钝化和活化

代码语言:javascript
复制
# 之前提到, 当服务器正常关闭,重启后,还可以再获取session(跟之前的一样)
这是因为tomcat已实现以下二个功能
1. 钝化(序列化: ObjectOutputStream) 保存
  当服务器正常关闭时,session中的数据,会序列化到硬盘 (持久化)
   序列化的目的: 将内存中对象或数据结构 保存 到硬盘 (编码: 看得懂 -> 看不懂)
    内存: 临时性存储设备, 断电了数据就消失
    硬盘: 持久性存储设备, 断电了数据依然在
    
2. 活化(反序列化: ObjectInputStream) 读取
  当服务器开启后,从磁盘文件中,将数据反序列化到内存中
   反序列化的目的: 将硬盘上的数据读取到内存,形成对象或数据结构 (解码: 看不懂 -> 看得懂)

备注: 钝化和活化的本质是序列化技术, 所以保存的存储数据类型需要实现serializable接口

我们使用的idea工具有坑:

代码语言:javascript
复制
1. 我们正常关闭tomcat,tomcat确实将session钝化到磁盘(下图的位置中的sessions.ser)
2. 坑: 但是在idea重启tomcat时,会默认删除之前保存的sessions.ser文件,造成tomcat没有活化数据
3. 解决: 设置idea重启时,不清除session会话(下图)

“支持钝化 ”

下面我们来演示一下 idea 工具的坑 到底是什么 坑!

1.5.1 首先查看 Idea 中操作 tomcat 的克隆空间(临时空间)

image-20210217221007671

根据这个路径,我们可以打开我们电脑的文件夹,如下:

image-20210217221059089

1.5.2 停止 tomcat ,查看钝化的效果,生成 session.ser 文件,将 session 信息保存在硬盘中

image-20210217221217785

停止 tomcat 后,那么将会将 session 保存如下:

image-20210217221540189

这个 SESSIONS.ser 就是 tomcat 钝化后的文件,提供后续启动 tomcat 的时候读取 session 数据,进行活化。

1.5.3 Idea默认启动的时候会删除之前钝化保存的文件,导致 tomcat 再次读取 session 数据 活化失败

image-20210217221824738

1.5.4 我们可以设置idea重启时,不清除session数据

image-20210217222001702

1.6 URL重写(了解)

代码语言:javascript
复制
# URL重写是为了解决cookie禁用问题
1. 问题: 浏览器是默认启用cookie,但是用户也可以禁用浏览器的Cookie(浏览器自带功能: 不允许浏览器保存cookie), 由于Session基于Cookie技术实现,所以一旦禁用了之后,Session功能就会出现问题

2. 解决: url重写技术
   response.encodeURL(path)
   会在url,拼接JSESSIONID
   
3. 备注: 开发中,一般我们是不关注禁用cookie的用户,若用户禁用了cookie,会给很多功能的实现带来很大的麻烦
1.6.1 首先禁止浏览器使用 cookie

image-20210217232943160

image-20210217233029543

此时点击 第二次访问,查看 Session 信息,由于没有 Session ID,导致没有 Session 信息,如下:

image-20210217233322018

1.6.2 修改第一次访问的请求 Servlet

index.html

image-20210217233653491

UrlOverrideServlet

image-20210217234101372

代码语言:javascript
复制
package com.lijw;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @author Aron.li
 * @date 2021/2/17 23:37
 */
@WebServlet("/UrlOverrideServlet")
public class UrlOverrideServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    /*
     * 正常session 有个前提:
     *   1. 浏览器需要携带 Jsessionid 过来
     *   2. 默认情况下: Jsessionid是用cookie保存的
     *   3. 用户在浏览器中设置: 禁用cookie  -> 浏览器不再保存cookie
     *
     *   解决:
     *       jsessionid 作为参数放在url后面
     *
     *      url = http://localhost:8081/Session02Servlet
     *
     *     重写url
     *           http://localhost:8081/Session02Servlet;jsessionid = ?
     *
     *          参数分割符 ?  , 还有分号
     *           url?name=value  ->  value = request.getParameter(name)
     *           url;name=value   不能上面的api获取, tomcat可以获取就行了
     * */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 创建session
        HttpSession session = request.getSession();
        session.setAttribute("sick", "很虚");
        String id = session.getId();
        System.out.println(id);

        //重写URL,携带sessionId参数
        String url = "Session02Servlet";
        String newUrl = response.encodeURL(url); //重写url,拼接jsessionid
        System.out.println(newUrl); //  Session02Servlet;jsessionid = ?

        //将重写后的URL,作为超链接显示在浏览器上
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().print("<a href='"+newUrl+"'>第二次去医院</a>" );
    }
}
1.6.3 再次测试禁用cookie的情况下,获取session数据

image-20210217234224356

image-20210217234305695

image-20210217234318230

image-20210217234336678

在后台成功读取到了 session 数据。

1.7 Session特点

代码语言:javascript
复制
# session是服务器端的会话技术
 作用: 在一次会话的多次请求之间共享数据
  从浏览器第一次向服务器发起请求建立会话, 直到其中一方断开为止会话结束
1. session存储数据在服务器
2. session存储任意类型的数据(Object)
3. session存储大小和数量没有限制(在服务器内存)
4. session存储相对安全

cookie和session的对比

1591287669163

cookie和session的选择

代码语言:javascript
复制
1. cookie将数据保存在浏览器端,数据相对不安全.
  建议敏感的或大量的数据不要放在cookie中,而且数据大小是有限制的
     成本低,对服务器要求不高
   
2. session将数据保存在服务器端内存,数据相对安全.
  数据的大小要比cookie中数据灵活很多
     成本较高,对服务器压力较大

二、 3大域对象总结

request < session < ServletContext

2.1 域对象方法

代码语言:javascript
复制
# 域对象方法都一致
1. 设置数据
  void setAttribute(String name, Object o) 
2. 获取数据
  Object getAttribute(String name) 
3. 删除数据
  void removeAttribute(String name)
  
# 小域对象可以获取大域对象

# 不同域对象: 生命周期不一样

2.2 生命周期

2.2.1 ServletContext域对象

代码语言:javascript
复制
* 何时创建
  服务器正常启动,项目加载时,创建
* 何时销毁
  服务器关闭或项目卸载时,销毁
* 作用范围
  整个web项目(共享数据)

2.2.2 HttpSession域对象

代码语言:javascript
复制
* 何时创建
  用户第一次调用request.getSession()方法时,创建【不太标准..】
  用户访问携带的jsessionid与服务器里的session不匹配时,就会创建的
* 何时销毁
  1. 服务器非正常关闭
  2. 未活跃状态30分钟
  3. 手动销毁 
* 作用范围
  一次会话中,多次请求间(共享数据)

# 会话的定义: 双方建立连接,连接期间的多次请求响应,直到一方断开连接为止
 (B/S) 从浏览器第一次访问这个服务器,期间多次请求响应,直到浏览器关闭为止 -> 狭义的一次会话
  cookie和session默认都是会话级别,都可以设置持久级别

2.2.3 HttpServletRequest域对象

代码语言:javascript
复制
* 何时创建
  服务器接收到请求时,创建      
* 何时销毁
  服务器做出响应后,销毁
* 作用范围
  一次请求中,多次请求转发间(共享数据)

2.3 小结

  • 能用小的不用大的:request(一次请求)<session(一次会话)<servletContext(应用全局) “因为生命周期长的域对象销毁时间比较晚,占用服务器内存时间太长 ”
  • 常用的场景:
    • 用户登录状态
    • 验证码
    • 购物车
    • request:一次请求中(请求转发共享)
    • session:存放当前会话的私有数据
    • servletContext:若需要所有的servlet都能访问到,才使用这个域对象. “一般情况下,web阶段很少使用这个域对象,在框架spring的学习中会涉及到 ”

三、 用户登录-验证码案例

3.1 用户登录(验证码)

需求

用户访问带有验证码的登录页面,输入用户名,密码以及验证码实现登录功能。

3.1.1 需求分析

1591781837951

3.1.2 代码实现

1.编写生成验证码的Servlet

image-20210218232805983

代码语言:javascript
复制
package com.lijw;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

/**
 * @author Aron.li
 * @date 2021/2/18 23:24
 */
@WebServlet("/CheckcodeServlet")
public class CheckcodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //  创建画布
        int width = 120;
        int height = 40;
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //  获得画笔
        Graphics g = bufferedImage.getGraphics();
        //  填充背景颜色
        g.setColor(Color.white);
        g.fillRect(0, 0, width, height);
        //  绘制边框
        g.setColor(Color.red);
        g.drawRect(0, 0, width - 1, height - 1);
        //  生成随机字符
        //  准备数据
        String data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
        //  准备随机对象
        Random r = new Random();
        //  声明一个变量 保存验证码
        String code = "";
        //  书写4个随机字符
        for (int i = 0; i < 4; i++) {
            //  设置字体
            g.setFont(new Font("宋体", Font.BOLD, 28));
            //  设置随机颜色
            g.setColor(new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255)));

            String str = data.charAt(r.nextInt(data.length())) + "";
            g.drawString(str, 10 + i * 28, 30);

            //  将新的字符 保存到验证码中
            code = code + str;
        }
        //  绘制干扰线
        for (int i = 0; i < 6; i++) {
            //  设置随机颜色
            g.setColor(new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255)));

            g.drawLine(r.nextInt(width), r.nextInt(height), r.nextInt(width), r.nextInt(height));
        }

        //  将验证码 打印到控制台
        System.out.println(code);

        // TODO:  将验证码放到session中
        request.getSession().setAttribute("code_session", code);

        //  将画布显示在浏览器中
        ImageIO.write(bufferedImage, "jpg", response.getOutputStream());
    }
}

在网页测试请求,看看验证码的效果如下:

image-20210218232906641

2.编写用户登录的页面 login.html

image-20210218233852836

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2>登录页面</h2>
    <hr>
    <form action="LoginServlet" method="post">

        <input type="text" name="username" placeholder="请输入用户名"> <br>
        <input type="password" name="password" placeholder="请输入密码" style="margin-top: 5px;"> <br>

        <div style="display: flex; margin: 5px 0;">
            <input type="text" name="code" placeholder="请输入验证码" >
            <img src="CheckcodeServlet" alt="" id="myimg" style="height: 25px; margin-left: 5px; ">
        </div>

        <input type="submit">

    </form>

    <script >
        var img = document.getElementById("myimg");
        img.onclick = function () {
            /*
            *   浏览器特点:
            *       动态修改了网页中的元素属性,浏览器自动加载
            *
            *   原理:
            *       通过在url中添加一个没有实际作用的参数来欺骗浏览器
            *           修改网页属性,浏览器自动加载
            * */
            let time = new Date().getTime() // 获取当前系统时间毫秒值
            img.src = "CheckcodeServlet?time=" + time
        }
    </script>
</body>
</html>

在浏览器访问页面效果如下:

image-20210218233938755

3.编写处理登录业务的 LoginServlet

image-20210218234843686

代码语言:javascript
复制
package com.lijw;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author Aron.li
 * @date 2021/2/18 23:40
 */
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //解决请求参数的中文乱码
        request.setCharacterEncoding("UTF-8");
        //解决响应中文乱码
        response.setContentType("text/html;charset=utf-8");

        //1. 接收验证码 code,判断验证码是否正确
        //1.1 从session中获取图片的验证码
        String code_session = (String)request.getSession().getAttribute("code_session");
        //1.2 获取请求的验证码参数
        String code = request.getParameter("code");
        //1.3 判断验证码是否正确
        if (!code.equalsIgnoreCase(code_session)) {
            // 如果验证码不正确,则什么都不处理,直接返回
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().print("验证码错误,请重新输入");
            return;
        }

        //1. 获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        //2. 业务处理
        if("jack".equalsIgnoreCase(username) && "123".equalsIgnoreCase(password)){
            //登录成功
            response.getWriter().write("登录成功");
        }else{
            //登录失败
            response.getWriter().write("登录失败");
        }

    }
}
4.测试用户登录
  • 登录成功的情况

image-20210218234941346

image-20210218234955269

  • 登录失败的情况

image-20210218235031275

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 海洋的渔夫 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 26. 会话技术-Session的使用
  • 一、 Session
    • 1.1 概述
      • 1.2 工作原理
        • Session基于Cookie技术实现
        • 方法介绍
        • 编写示例代码
      • 1.3 Session细节
        • 1.4 session的持久化
          • 1.5 session的钝化和活化
            • 1.6 URL重写(了解)
              • 1.7 Session特点
              • 二、 3大域对象总结
                • 2.1 域对象方法
                  • 2.2 生命周期
                    • 2.2.1 ServletContext域对象
                    • 2.2.2 HttpSession域对象
                    • 2.2.3 HttpServletRequest域对象
                  • 2.3 小结
                  • 三、 用户登录-验证码案例
                    • 3.1 用户登录(验证码)
                      • 3.1.1 需求分析
                      • 3.1.2 代码实现
                  相关产品与服务
                  验证码
                  腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档