Cookie和Session
序
众所周知,Http协议是无状态的,也就意味着,针对浏览器与服务器之间的请求和响应(也叫会话),当两者之间的会话结束时,服务器端并不会记忆客户端(浏览器)曾经访问过。
但是,在实际应用程序开发中,有些业务需要浏览器和服务器之间能够保持会话。比如常见的登录业务,在同一个浏览器下,当用户第一次登录成功并进入首页时,下次再使用同一个浏览器访问首页时,则不需要再登录。而要实现下次访问不再登录时,需要让服务端能够识别曾经访问过它的浏览器,这就需要会话跟踪技术来实现。分别是cookie
和session
。
第一章: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设置为
"/"
- 如果要共享,则可以将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可以共享
- 如:
- 如果设置一级域名相同,那么多个服务器之间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一般用于存出少量的不太敏感的数据。
- 常见使用场景
- 判断用户是否登陆过网站,以便下次登录时能够实现自动登录(或者记住密码)。如果我们删除cookie,则每次登录必须从新填写登录的相关信息。
- 保存上次登录的时间等信息
- 保存上次查看的页面
- 浏览计数
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何时销毁
- 服务器关闭
- session对象调用
invalidate()
- 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自动完成以下工作。
- session的钝化:在服务器正常关闭之前,将session对象系列化到硬盘上。
- session的活化:在服务器启动后,将session文件转化为内存中的session对象即可。
2.5-Session的特点
- session用于存储一次会话的多次请求的数据,存在服务器端。
- session可以存储任意类型,任意大小的数据。
2.6-Session和Cookie的区别
- session存储数据在服务器端,Cookie在客户端。
- session没有数据大小限制,Cookie有。
- session数据安全,Cookie相对不安全。
2.7-Session的应用场景
Session用于保存每个用户的专用信息,变量的值保存在服务器端,通过SessionID来区分不同的客户。
- 网上商城中的购物车
- 保存用户登录信息
- 将某些数据放入session中,供同一用户的不同页面使用。
- 防止用户非法登录
2.8-Session的缺点
(1)Session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大;
(2)依赖于cookie(sessionID保存在cookie),如果禁用cookie,则要使用URL重写,不安全;
(3)创建Session变量有很大的随意性,可随时调用,不需要开发者做精确地处理,所以,过度使用session变量将会导致代码不可读而且不好维护。
2.9-案例
需求
登录页面,用户输入用户名和密码以及验证码,验证登录是否成功。
登录失败的提示信息:
- 验证码错误
- 用户名或密码错误
登录成功到首页,显示欢迎XXX
实现步骤
概述
- 准备数据信息(创建数据库、表、设置数据)
- 新建项目,准备导入相关的jar包(Druid相关jar包和JdbcTemplate对象需要的相关jar包)
- 配置文件设置数据库连接信息
数据库准备
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>