Cookie和Session


​ 众所周知,Http协议是无状态的,也就意味着,针对浏览器与服务器之间的请求和响应(也叫会话),当两者之间的会话结束时,服务器端并不会记忆客户端(浏览器)曾经访问过。

​ 但是,在实际应用程序开发中,有些业务需要浏览器和服务器之间能够保持会话。比如常见的登录业务,在同一个浏览器下,当用户第一次登录成功并进入首页时,下次再使用同一个浏览器访问首页时,则不需要再登录。而要实现下次访问不再登录时,需要让服务端能够识别曾经访问过它的浏览器,这就需要会话跟踪技术来实现。分别是cookiesession

第一章:Cookie

1.1-Cookie概述

Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 。

总而言之,cookie就是客户端会话技术,可以将数据以存储在客户端。

1.2-Java操作Cookie

创建cookie

在服务端创建Cookie。

创建方式:Cookie 变量名 = new Cookie(name,value);

  • name,cookie的名称。
  • value,cookie的值。

读取cookie中的name和value

  • cookie对象.getName();
  • cookie对象.value();

保存cookie到客户端

保存方式:response对象.addCookie(Cookie cookie) ;

获取cookie

服务端接收客户端发送过来的cookie,通过request对象获取。

方法:Cookie[] request.getCookies(),返回一个Cookie数组。

设置cookie的生命周期

在服务端中可以指定cookie在客户端存活的时长。

cookie对象中提供了cookie的存活时长相关方法。

方法:cookie对象.setMaxAge(int seconds);

  • seconds,单位是秒。
  • 正数:将Cookie数据写到硬盘的文件中。持久化存储。并指定cookie存活时间,时间到后,cookie文件自动失效
  • 负数:默认值,关闭浏览器时,会自动失效。
  • 零:0,表示删除cookie。

cookie共享问题

情况1:假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?

  • 默认情况下cookie不能共享。
  • 可以通过cookie对象的setPath(String path)方法:设置cookie的获取范围。默认情况下,设置的时当前的虚拟目录。
    • 如果要共享,则可以将path设置为"/"

情况2:不同的tomcat服务器间cookie共享问题?

  • 域名:
    • 一级域名:如:baidu.com
    • 二级域名:如:news.baidu.com、tieba.baidu.com
      • 一般会部署在不同的服务器上。
  • 方法:setDomain(String path)
    • 如果设置一级域名相同,那么多个服务器之间cookie可以共享
      • 如:setDomain(".baidu.com"),那么tieba.baidu.com和news.baidu.com中cookie可以共享

代码

Base01.Java保存cookie到客户端

package cn.leilei.base;

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

@WebServlet("/base01")
public class Base01 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 创建cookie对象
    Cookie cookie = new Cookie("userName","张三");
    // 设置cookie的时间
    cookie.setMaxAge(60*3);
    // 设置cookie路径
    cookie.setPath("/");
    Cookie cookie1 = new Cookie("id","10010");
    // 设置cookie的时长
    cookie1.setMaxAge(60*3);
    // 发送cookie对象给客户端
    response.addCookie(cookie);
    response.addCookie(cookie1);
  }

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

Base02.java服务端读取客户端发送的cookie

package cn.leilei.base;

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

@WebServlet("/base02")
public class Base02 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 读取cookie
    Cookie[]cookies = request.getCookies();
    // 循环遍历读取
    for (Cookie cookie : cookies) {
      // 获取cookie的名称
      String name = cookie.getName();
      // 获取cookie的值
      String value = cookie.getValue();
      System.out.println(name + ":" + value);
    }
  }

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

1.3-Cookie的原理

基于响应头set-cookie和请求头cookie实现

1.4-Cookie的特点

  • cookie存储数据在客户端浏览器。
  • 浏览器对于单个cookie 的大小有限制(4kb) 以及 对同一个域名下的总cookie数量也有限制(20个)

1.5-Cookie的应用场景

  • cookie一般用于存出少量的不太敏感的数据。
  • 常见使用场景
    1. 判断用户是否登陆过网站,以便下次登录时能够实现自动登录(或者记住密码)。如果我们删除cookie,则每次登录必须从新填写登录的相关信息。
    2. 保存上次登录的时间等信息
    3. 保存上次查看的页面
    4. 浏览计数

1.6-Cookie的缺点

(1)可能被删除,被禁用;

(2)安全性不高,纯文本形式存储,如存储密码则需加密处理;

(3)大小受限,容量4kb,相当于4000个英文字母;

(4)不同浏览器不相通,不同域中的cookie不共享;

(5)每次都需传递cookie给服务器,浪费带宽;

(6)cookie数据有路径(path)的概念,可以限制cookie只属于某个路径下。

1.7-案例

需求

打开页面,若不是第一次,则显示访问的次数及上次访问时间。

第一次访问。

下一次访问。

代码

Cookie操作的封装代码

package cn.leilei.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

public class CookieUtils {
  // 添加Cookie
  public static Cookie addCookie(String name,String value){
    Cookie cookie = null;
    try {
      value = URLEncoder.encode(value,"utf-8");
      cookie = new Cookie(name,value);
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
    return cookie;
  }
  // 根据名字读取对应的cookie值
  public static String getValue(HttpServletRequest request, String name){
    Cookie[]cookies = request.getCookies();
    String value = null;
    if(cookies!=null){
      for (Cookie cookie : cookies) {
        if(cookie.getName().equals(name)){
          value = cookie.getValue();
          try {
            value = URLDecoder.decode(value,"utf-8");
          } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
          }
          break;
        }
      }
    }
    return value;
  }
}

显示页面的jsp代码

<%@ page import="java.util.Date" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="cn.leilei.utils.CookieUtils" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>

    <style>
        body{
            background: #000;
        }
        h1{
           color:gold;
        }
        h2{
            color: white;
        }
    </style>
</head>
<body>
<%
    // 检测是否有lastTime对应的cookie值
    String value = CookieUtils.getValue(request, "lastTime");
    if (value == null) {
        // 说明第一次,访问该页面
        out.write("<h1>欢迎光临,您是第1次访问。</h1>");
        setInfo(response, "1");
    } else {
        // 说明之前访问过
        // 获取之前的访问时间
        String time = CookieUtils.getValue(request, "lastTime");
        // 获取之前的访问次数
        String count = CookieUtils.getValue(request, "count");
        count = (Integer.parseInt(count) + 1) + "";
        // 显示消息
        out.write("<h1>欢迎光临,您是第" + count + "次访问。</h1>");
        out.write("<h2>上次访问时间是:" + time);
        // 重新设置cookie
        setInfo(response, count);

    }
%>
<%--封装设置cookie信息方法--%>
<%!
    // 封装方法
    private void setInfo(HttpServletResponse response, String count) {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
        String v = format.format(date);
        Cookie cookie1 = CookieUtils.addCookie("lastTime", v);
        cookie1.setMaxAge(60 * 60 * 24 * 30);
        response.addCookie(cookie1);
        Cookie cookie2 = CookieUtils.addCookie("count", count);
        cookie2.setMaxAge(60 * 60 * 24 * 30);
        response.addCookie(cookie2);
    }
%>
</body>
</html>

第二章:Session

2.1-Session概述

​ Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在Session对象中。有关使用Session 对象的详细信息,注意会话状态仅在支持cookie的浏览器中保留。

2.2-Java操作Session

获取Session对象

可以通过request对象获取Session对象

获取方式: HttpSession session = request.getSession();

使用Session对象

  • 设置
    • 方法:void setAttribute(String name, Object value)
    • 参数:
      • name,名称
      • value,值,可以是任意类型的值。
  • 获取
    • 方法:Object getAttribute(String name)
    • 参数:
      • name,名称
  • 移除
    • 方法:void removeAttribute(String name)
    • 参数:
      • name,名称

代码

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;

@WebServlet("/s1")
public class ServletS1 extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 获取Session对象
    HttpSession session = request.getSession();
    // 添加session值
    session.setAttribute("userName","张三");
    // 读取session
    String value = (String)session.getAttribute("userName");
    System.out.println(value);  // 张三
    // 移除session
    session.removeAttribute("userName");
    // 重写读取
    String value2 = (String)session.getAttribute("userName");
    System.out.println(value2); // null
  }

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

2.3-Session原理

Session的实现依赖于Cookie。

2.4-Session何时销毁

  1. 服务器关闭
  2. session对象调用invalidate()
  3. session默认失效时间 30分钟

可以通过配置文件如:web.xml配置session的默认失效如下

<session-config>
   <session-timeout>30</session-timeout>
</session-config>

当客户端关闭后,服务器不关闭,两次获取session是否为同一个?

默认情况不是。

如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。如下:

Cookie c = new Cookie("JSESSIONID",session.getId());
c.setMaxAge(60*60);
response.addCookie(c);

客户端不关闭,服务器关闭后,两次获取的session是同一个吗?

不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作。

  1. session的钝化:在服务器正常关闭之前,将session对象系列化到硬盘上。
  2. session的活化:在服务器启动后,将session文件转化为内存中的session对象即可。

2.5-Session的特点

  1. session用于存储一次会话的多次请求的数据,存在服务器端。
  2. session可以存储任意类型,任意大小的数据。

2.6-Session和Cookie的区别

  1. session存储数据在服务器端,Cookie在客户端。
  2. session没有数据大小限制,Cookie有。
  3. session数据安全,Cookie相对不安全。

2.7-Session的应用场景

Session用于保存每个用户的专用信息,变量的值保存在服务器端,通过SessionID来区分不同的客户。

  1. 网上商城中的购物车
  2. 保存用户登录信息
  3. 将某些数据放入session中,供同一用户的不同页面使用。
  4. 防止用户非法登录

2.8-Session的缺点

(1)Session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大;

(2)依赖于cookie(sessionID保存在cookie),如果禁用cookie,则要使用URL重写,不安全;

(3)创建Session变量有很大的随意性,可随时调用,不需要开发者做精确地处理,所以,过度使用session变量将会导致代码不可读而且不好维护。

2.9-案例

需求

登录页面,用户输入用户名和密码以及验证码,验证登录是否成功。

登录失败的提示信息:

  1. 验证码错误
  2. 用户名或密码错误

登录成功到首页,显示欢迎XXX

实现步骤

概述

  1. 准备数据信息(创建数据库、表、设置数据)
  2. 新建项目,准备导入相关的jar包(Druid相关jar包和JdbcTemplate对象需要的相关jar包)
  3. 配置文件设置数据库连接信息

数据库准备

CREATE DATABASE db5;
USE db5;
CREATE TABLE USER(
	id INT PRIMARY KEY AUTO_INCREMENT,
	userName VARCHAR(32) NOT NULL,
	pwd VARCHAR(32) NOT NULL
);
INSERT INTO USER VALUES(NULL,zhagnsan,123456),(NULL,lisi,abcdef);

User实体类

package cn.myweb.domain;
/**
 * 用户实体类
 * @author Bruce
 */
public class User {
  private int id;
  private String userName;
  private String pwd;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getUserName() {
    return userName;
  }

  public void setUserName(String userName) {
    this.userName = userName;
  }

  public String getPwd() {
    return pwd;
  }

  public void setPwd(String pwd) {
    this.pwd = pwd;
  }

druid.propertites配置文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/db5
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000

DruidUtils数据库连接池操作类

package cn.myweb.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DruidUtils {
  private static DataSource ds;
  private static InputStream is;
  static {
    Properties pro = new Properties();
    try {
      // 读取配置文件
      is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
      pro.load(is);
      // 创建数据库连接池对象
      ds = DruidDataSourceFactory.createDataSource(pro);
    } catch (IOException e) {
      e.printStackTrace();
    } catch (Exception e) {
      e.printStackTrace();
    }finally {
      if(is!=null){
        try {
          is.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

  }
  // 获取Connection对象
  public static Connection getConnection() throws SQLException {
    return ds.getConnection();
  }
  // 释放资源
  public static void close(Statement sta, Connection con, ResultSet rs) {
    if(sta!=null){
      try {
        sta.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }

    }
    if(con!=null){
      try {
        con.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
    if(rs!=null){
      try {
        rs.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }
  public static void close(Statement sta, Connection con){
    close(sta,con,null);
  }
  // 获取连接池对象
  public static DataSource getDataSource(){
    return ds;
  }
}

UserDao操作数据库用户表的类

package cn.myweb.Dao;

import cn.myweb.domain.User;
import cn.myweb.utils.DruidUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * 数据库User表相关操作
 */
public class UserDao {
  // 获取JdbcTemplate对象
  JdbcTemplate jt = new JdbcTemplate(DruidUtils.getDataSource());
  public User checkUser(User loginUser){
    User user = null;
    String sql = "select * from user where userName=? and pwd=?";
    try {
      user = jt.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class),loginUser.getUserName(),loginUser.getPwd());
    }catch (Exception e){
      e.printStackTrace();
      return null;
    }
    return user;
  }
}

生成验证码的ServletCheckCode类

package cn.myweb.myServlet;

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 javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

@WebServlet("/ServletCheckCode")
public class ServletCheckCode extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    int width = 100;
    int height = 50;
    // 1.创建一个图片对象
    BufferedImage bImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);

    // 2.美化图片
    // 2.1 填充颜色
    // 获取画笔
    Graphics graphics = bImg.getGraphics();
    // 设置颜色
    graphics.setColor(Color.darkGray);
    // 填充颜色
    graphics.fillRect(0, 0, width, height);
    // 2.2 绘制边框
    graphics.setColor(Color.pink);
    graphics.drawRect(0, 0, width - 1, height - 1);
    // 2.3 绘制文字
    Random random = new Random();
    String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
    StringBuilder sb = new StringBuilder();
    for (int i = 1; i <= 4; i++) {
      char charStr = str.charAt(random.nextInt(str.length()));
      sb.append(charStr);
      graphics.drawString(charStr + "", width / 5 * i, height / 2 + 6);
    }
    String code = sb.toString();
    // 将code存入session;
    HttpSession session = request.getSession();
    session.setAttribute("code",code);
    // 2.4 绘制干扰线
    for (int i = 0; i < 10; i++) {
      float[] fs = {};
      graphics.setColor(Color.cyan);
      int x1 = random.nextInt(width);
      int x2 = random.nextInt(width);
      int y1 = random.nextInt(height);
      int y2 = random.nextInt(height);
      graphics.drawLine(x1, y1, x2, y2);
    }
    // 3.将图片输出到页面
    ImageIO.write(bImg, "jpg", response.getOutputStream());
  }

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

ServletLogin登录处理类

package cn.myweb.myServlet;

import cn.myweb.Dao.UserDao;
import cn.myweb.domain.User;
import org.apache.commons.beanutils.BeanUtils;

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;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

@WebServlet("/ServletLogin")
public class ServletLogin extends HttpServlet {
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 设置request编码格式
    request.setCharacterEncoding("utf-8");
    // 读取参数
    Map<String, String[]> parameterMap = request.getParameterMap();
    // 创建User对象
    User loginUser = new User();
    // 根据参数为User对象赋值
    try {
      BeanUtils.populate(loginUser,parameterMap);
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
    // 获取用户传入的验证码
    String CCode = request.getParameter("code");
    // 获取服务器本地验证码
    String SCode = (String)request.getSession().getAttribute("code");
    // 删除服务器验证码,保证每次提交验证码都是新的
    request.getSession().removeAttribute("code");
    // 检测验证码是否正确
    if(SCode!=null&&SCode.equalsIgnoreCase(CCode)){
      // 检测用户名和密码是否正确
      User user = new UserDao().checkUser(loginUser);
      if(user==null){
        request.setAttribute("login_error","用户名或密码错误!");
        request.getRequestDispatcher("/login.jsp").forward(request,response);
      }else {
        request.getSession().setAttribute("user",user);
        response.sendRedirect("/myweb01/index.jsp");
      }
    }else {
      // 转发到登录页,告诉用户验证码不正确!
      request.setAttribute("code_error","验证码输入不正确!");
      request.getRequestDispatcher("/login.jsp").forward(request,response);
    }


  }

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

登录页面login.jsp

<%--
  Created by IntelliJ IDEA.
  User: Bruce
  Date: 2019/12/26
  Time: 19:00
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Login</title>
    <style>
        table{
            border-collapse: collapse;
            width: 585px;
            margin: 0 auto;
        }
        td {
            padding:20px;
        }
        input {
            height: 50px;
        }
        input[type=text],input[type=password]{
            width:300px;
        }
        table tr:last-child {
            text-align: center;
        }
        input[type=submit]{
            width: 100px;
        }
        div {
            color: red;
            text-align: center;
        }
    </style>
</head>
<body>
<form method="post" action="/myweb01/ServletLogin">
    <table>
        <tr>
            <td>用户名:</td>
            <td colspan="2"><input type="text" name="userName" placeholder="请输入用户名"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td colspan="2"><input type="password" name="pwd" placeholder="请输入密码"></td>
        </tr>
        <tr>
            <td>验证码:</td>
            <td><input type="text" name="code" placeholder="请输入验证码"></td>
            <td><img src="/myweb01/ServletCheckCode" alt="验证码" title="看不清?点击换一张"></td>
        </tr>
        <tr>
            <td colspan="3"><input type="submit" value="登录"></td>
        </tr>
    </table>
</form>
<script>
    var imgNode = document.querySelector("img");
    imgNode.onclick = function(){
        this.src = "/myweb01/ServletCheckCode?time=" + new Date().getTime();
    };
</script>
<div><%= request.getAttribute("code_error")==null?"":request.getAttribute("code_error")%></div>
<div><%= request.getAttribute("login_error")==null?"":request.getAttribute("login_error")%></div>
</body>
</html>

首页页面index.jsp

<%@ page import="cn.myweb.domain.User" %><%--
  Created by IntelliJ IDEA.
  User: Bruce
  Date: 2019/12/26
  Time: 18:49
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>首页</title>
    <style>
        span {
            color: green;
        }
    </style>
</head>
<body>
<h1>首页</h1>
<% User user = (User) session.getAttribute("user"); %>
<% if (user != null) {%>
<h2>欢迎来<span>【<%=user.getUserName()%>】</span>到MyWeb</h2>
<%}%>

</body>
</html>

posted @ 2019-12-27 00:39  雷哒哒  阅读(316)  评论(0编辑  收藏  举报