JavaWeb会话技术
HTTP通信协议的特点:
1.应答式的协议,只能是客户端先发送请求然后服务器作出响应。
2.明文传输,传输的数据是明文,没有做加密之类处理
3.简单快速。HTTP协议相对简单,所以传输较快。
4.无状态,HTTP通信,不会记录客户端的请求信息。默认通信完成,传输断开。
基于无状态这点,如果客户端和服务器需要进行二次会话,且可能需要之前会话的数据这样的需求场景。
所以,WEB技术中,提供了2种解决方案,这两种解决方案就被称之为会话技术。分别是:Session和Cookie。
1.如果一方需要之前沟通的记录,在生活中是怎么处理的?
方案一: 将数据存储起来,需要时进行查看。Session
方案二:重新将之前的记录发送一次。Cookie
2.WEB中的Session技术
WEB中的Session技术,是在服务器创建一个Session容器,可以将数据存储在这个容器,然后将查找到这个容器key,给客户端,那么每次客户端来请求服务器时,可以将这个key带给服务器,服务器就可以根据这个key,找到客户端寄存在服务器的Session(数据容器),然后从session中获取存储的数据。
例如: 在超市里,将物品进行寄存,有一个寄存的号码牌,需要时,可以根据号码牌,拿到寄存的物品。
注意:
1.session是在服务器的内存中,开辟了一存储空间,默认是有效时间是30分钟,若没有主动释放,则30分钟会释放内存。
2.session容器是一个map结构的容器,根据key查找对应的session,若key不对,那么session将找不到(找不到不代表它消失了)。一般关闭浏览器,或者使用新的浏览器,服务器会分配一个新的key给浏览器,此时就找不到之前的那个session了,但是之前的session是在内存中的。
3.客户端从存储session对应的key是使用cookie技术存储的,所以session技术也是依赖cookie技术。
2.1.session使用
session作为一个会话对象,其中可以存储多次请求需要共享的数据。例如:验证码,当前登录用户都可以存在session。所以首先要有session容器。
在调试时发现,当第一次访问jsp页面时,会有Cookie,且Cookie中存在JSESSIONID的值,但是访问servlet的时候没有,因为JSP默认会创建session对象(session是JSP的内置对象之一),但是session是不会主动创建session对象的。所以在servlet中使用session,首先要创建一个session对象,而session与客户端的请求关联的,因为session表示会话,会话包含多个请求,所以需要根据请求对象,找到关联的session对象。
以上图片,浏览器中Cookie中JSESSIONID对应的值,与服务器中产生的sesion对象的ID值一致,说明服务器将自己创建的session对象的ID值给了浏览器。且使用的是Cookie。所以session是依赖Cookie,因为需要使用Cookie存储自己的唯一标识,便于浏览器找到对应的Session.
package com.sxt.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @ClassName: SessionController
* @Description: 演示session的API
* @author: Mr.T
* @date: 2020年2月14日 上午11:36:30
*/
public class SessionController extends HttpServlet {
private static final long serialVersionUID = -6539113475281947079L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("service方法执行了");
// getSession() : 返回与当前请求关联的session 如果没有则创建一个新的session
// getSession(create) : 返回当前关联的session对象, 如果create 为true,当前session不存在时创建一个,为false则返回null
//一般都是使用 getSession()
HttpSession session = req.getSession();
System.out.println("session的ID:"+session.getId());
//resp.getWriter().println("Hello Session");
//session中核心方法
//session 作为作用域 其核心作用是数据管理
//setAttribute(name, value) :向session 作用域中存储数据
//getAttribute(name) : 从session作用域中获取数据
//getAttributeNames() : 从session作用域中获取所有存储数据的name值
//session生命周期方法
//setMaxInactiveInterval(long) : 设置session 有效时间 单位是秒
//如果设置为0 或者 负数,那么session 将永远不会失效 不建议 因为session占服务器内存
// tomcat 服务器 默认是 30分钟
//session.setMaxInactiveInterval(interval);
//invalidate() : 是这个session不关联任何对象,即取消session和request的关联
//这样当前request 就无法找到这个session
//session.invalidate();
}
}
2.2session目前的不足
session是存储在服务器的内存中,所以session只支持单应用,而不支持负载形式的应用架构。
所以session只支持单应用的架构,只支持只有一台服务器的架构。
2.3.使用session记录当前用户
一般在开发中,会将当前登录用户,存储在内存中,便于其他地方使用。而在web程序,会将当前的登录用户,存储在session中。然后,在其他页面或者servlet中,若要使用当前用户信息,则可以直接从session中获取,不必根据用户名和密码重新查询,只需要登录一次即可。
代码:
package com.sxt.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.sxt.pojo.User;
/**
* @ClassName: UserController
* @Description: 用户控制类
* @author: Mr.T
* @date: 2020年2月14日 下午2:40:11
*/
public class UserController extends HttpServlet {
private static final long serialVersionUID = 7723086583204133716L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
String password = req.getParameter("password");
//根据用户名和密码查询用户
//假设 用户名和密码 值都是 admin 当做登录成功
if("admin".equals(userName) && "admin".equals(password)) {
//将用户信息存在session中
User user = new User(1, "admin", "password","韩梅梅");
//放入session 便于在当前会话中 任何页面或者servlet 都可以直接拿到当前登录用户信息
req.getSession().setAttribute("user", user);
}
//去登录成功页面
resp.sendRedirect("success.jsp");
}
}
package com.sxt.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.sxt.pojo.User;
/**
* @ClassName: TestController
* @Description: 用于测试 可以从session中获取当前登录用户
* @author: Mr.T
* @date: 2020年2月14日 下午2:47:58
*/
public class TestController extends HttpServlet {
private static final long serialVersionUID = -5123674255870472885L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
System.out.println("当前登录的用户是:"+user.getRealName());
}
}
package com.sxt.pojo;
public class User {
private Integer userId;
private String userName;
private String password;
private String realName;
public User() {}
public User(Integer userId, String userName, String password, String realName) {
super();
this.userId = userId;
this.userName = userName;
this.password = password;
this.realName = realName;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
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;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
登录页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Insert title here
欢迎:${user.realName}
2.4.使用session存储验证码
一般在web网页上看到的图片,其实是服务器将图片数据使用字节流,输出给浏览器,然后浏览器拿到数据后,将数据解析成图片,渲染在页面。
那么,在页面上看到的验证码图片,其实就是服务器生成的一张图片,然后将图片使用字节流发送给了,浏览器。然后浏览器解析。并且,在处理登录请求的servlet时,拿到用户输入的验证码和之前产生的验证码要进行比对。
那么此时,就是在登录的servlet,要使用造验证码的servlet产生的数据,但是造验证码servlet程序已经执行完成了,内存已经释放了。之前造的验证码字符串已经消失了,无法进行比对。基于这样的情况,所以一般是将产生验证码的字符串,存储在session中,这样在登录请求的servlet中,也可以拿到之前产生的验证码,从而进行比对。
产生验证码:
package com.sxt.controller;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: CheckCodeController
* @Description: 产生验证码的servlet
* @author: Mr.T
* @date: 2020年2月14日 下午3:27:44
*/
public class CheckCodeController extends HttpServlet {
private static final long serialVersionUID = -1597171426922505140L;
/**
* 验证码池
*/
static String[] codes = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d"};
/**
* 验证码的本质 其实就是 一个有随机字符串的图片
* 服务器,将图片转化为字节流输出给浏览器
* 浏览器解析流数据,将图片渲染在页面
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//要使用Java程序 造一个含有随机字符串的图片
//1. 创建一个背景图 画布
BufferedImage image = new BufferedImage(200, 100, BufferedImage.TYPE_INT_RGB);
//2. 获取一个跟画布关联的画笔
Graphics graphics = image.getGraphics();
//3.使用画笔绘制信息
//为画笔设置颜色
graphics.setColor(Color.WHITE);
//绘制一个填充的矩形 将默认黑色的画布 涂成白色
graphics.fillRect(0, 0, 200, 100);
//1. 获取验证码字符串 4个字符长度的验证码
String code = getCode(4);
//将验证码 放入session 便于在其他地方使用
req.getSession().setAttribute("code", code);
// 设置画笔的字体 宋体 加粗 倾斜 35磅值
Font font = new Font("宋体", Font.BOLD | Font.ITALIC, 60);
graphics.setFont(font);
//2.根据循环将字符 绘制到图片上
for (int i = 0; i < code.length(); i++) {
//随机改变画笔颜色
Color c = getColor();
graphics.setColor(c);
//获取具体的字符
String str = code.charAt(i)+"";
//让文字的横坐标值变大,从而向右侧移动
int x = 20 + i*40;
graphics.drawString(str, x,65);
}
ImageIO.write(image, "jpg", resp.getOutputStream());
}
/**
* @Title: getColor
* @author: Mr.T
* @date: 2020年2月14日 下午3:54:56
* @Description: 随机产生颜色
* @return
* @return: Color
*/
private Color getColor() {
//因为RGB颜色 是由 0-255,0-255,0-255 的数值组成
Random random = new Random();
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
return new Color(r, g, b);
}
/**
* @Title: getCode
* @author: Mr.T
* @date: 2020年2月14日 下午3:57:55
* @Description: 产生随机字符串
* @param count 验证码的个数
* @return
* @return: String
*/
private String getCode(int count) {
String code = "";
Random random = new Random();
for (int i = 0; i < count; i++) {
int index = random.nextInt(codes.length);
code = code + codes[index];
}
return code;
}
/**
* @Title: method
* @author: Mr.T
* @date: 2020年2月14日 下午4:03:38
* @Description: 绘制验证码相关API的演示
* @return: void
*/
private void method(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 要使用Java程序 造一个含有随机字符串的图片
// 1. 创建一个背景图 画布
BufferedImage image = new BufferedImage(200, 100, BufferedImage.TYPE_INT_RGB);
// 2. 获取一个跟画布关联的画笔
Graphics graphics = image.getGraphics();
// 3.使用画笔绘制信息
// 为画笔设置颜色
graphics.setColor(Color.WHITE);
// 绘制一个填充的矩形 将默认黑色的画布 涂成白色
graphics.fillRect(0, 0, 200, 100);
// 可以在白色画布上绘制文字 注意 此时画笔是白色 绘制信息也是白色 所以需要改变画笔的颜色
// 画笔设置为红色
graphics.setColor(Color.RED);
// 设置画笔的字体 宋体 加粗 倾斜 35磅值
Font font = new Font("宋体", Font.BOLD | Font.ITALIC, 30);
graphics.setFont(font);
graphics.drawString("情人节快乐", 10, 55);
// 绘制干扰线 2点 连起来 就是一条线
graphics.drawLine(0, 0, 200, 100);
// 使用输出流 将图片数据输出给浏览器
ImageIO.write(image, "jpg", resp.getOutputStream());
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
登录页面
使用Hutool生成验证码:
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/**
* width : 图片的宽度
* height : 图片高度
* codeCount : 验证码的个数
* circleCount : 干扰元素个数
*/
LineCaptcha checkObj = CaptchaUtil.createLineCaptcha(200, 100, 4, 100);
//获取具体的验证码字符串
String code = checkObj.getCode();
//将生成的验证码 放入session
req.getSession().setAttribute("code", code);
//将验证码输出到页面
checkObj.write(resp.getOutputStream());
}
校验验证码:
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取用户输入的验证码
String checkCode = req.getParameter("checkCode");
//从session中获取产生时存储的验证码
Object code = req.getSession().getAttribute("code") ;
//对验证码进行对比
if(checkCode == null || !checkCode.equals(code.toString())) {
System.out.println("验证码错误!");
resp.sendRedirect("login.jsp");
return;
}
String userName = req.getParameter("userName");
String password = req.getParameter("password");
//根据用户名和密码查询用户
//假设 用户名和密码 值都是 admin 当做登录成功
if("admin".equals(userName) && "admin".equals(password)) {
//将用户信息存在session中
User user = new User(1, "admin", "password","韩梅梅");
//放入session 便于在当前会话中 任何页面或者servlet 都可以直接拿到当前登录用户信息
req.getSession().setAttribute("user", user);
}
//去登录成功页面
resp.sendRedirect("success.jsp");
}
3.WEB中的Cookie技术
在WEB中,Cookie技术是将需要的数据,由服务器写入(响应)到浏览器中(内存中,一个文件中),然后,浏览器接下来的请求,都会将这些数据通过请求头,带给服务器。服务器可以通过解析请求头,从而解析这些数据。这种技术方案就被称之为Cookie.
注意:
- Cookie的存储2种方式,一种是内存中一种磁盘文件中,这两种方式是Cookie生命周期的体现。默认Cookie的生命周期,是在内存中,基本是一次会话。
- Cookie是由服务器,通过响应对象,将数据通知给客户端,让客户端在本地进行一定的存储,且下次来请求带过来。
3.1.Cookie的使用
-
创建Cookie
Cookie cookie1 = new Cookie("name","value"); // cookie是map结构
-
通过response对象,将创建的cookie给浏览器
resp.addCookie(cookie1); //通过响应对象,设置set-cookie的响应头,浏览器拿到这个响应头后 ,会将响应头找中的数据保存到浏览器的内存中或者本地
以后的请求,浏览器会将cookie信息带给服务器,服务器可以从请求头中,解析cookie信息。
代码示例:
/**
* @ClassName: CookieController
* @Description: 用于演示Cookie相关的API
* @author: Mr.T
* @date: 2020年2月16日 上午10:47:39
*/
public class CookieController extends HttpServlet {
private static final long serialVersionUID = 2285648241402004645L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//cookie是由服务器 告诉客户端 要存储哪些数据
//cookie是由服务器创建 然后 服务器将这个cookie 给客户端 客户端将cookie数据进行存储
//之后的请求,都会将cookie数据带给服务器
//1. 创建Cookie --- cookie数据结构 map接口
Cookie cookie1 = new Cookie("cookieName", "cookieValue");
//设置cookie的有效期 单位是秒 如果设置为 0 则表示要删除这个cookie
//如果设置了有效期,浏览器会将cookie存储在自己本地的文件中,如果不清除,且没有过有效期,那么cookie就有效
//之后请求,会将数据带给服务器
cookie1.setMaxAge(60*2);//2分钟
//设置cookie是否允许浏览器进行操作 由于cookie是存在浏览器中,浏览器可以通过js对cookie进行数据操作
//servlet 3.0 后 setHttpOnly(true) : 只有HTTP可以操作Cookie 浏览器无法操作Cookie
cookie1.setHttpOnly(true);
//注意,cookie 默认只对当前项目生效 有效路径 当前项目 域名 项目名
//但是实际中,存在子域名,当需要多个子域名cookie也生效,则需要设置这个属性
//cookie1.setDomain(pattern);
//注意,默认只对项目及项目的子目录生效。限制生效的URI,通过setPath进行设置
//cookie1.setPath(uri);
//2. 通过响应对象 将cookie 给客户端
resp.addCookie(cookie1);
resp.getWriter().print("Hello Cookie");
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//cookie数据 是浏览器 通过请求头的信息 将数据传给服务器的,所以解析cookie 要从请求对象中解析
// 获取所有的cookie
Cookie[] cookies = req.getCookies();
//判断cookie是否存在
if(cookies != null) {
//只能通过遍历的方式进行判断 遍历所有cookie 获取每个cookie的name值 对比name 从而拿到自己需要的cookie对象
for (Cookie cookie : cookies) {
//获取cookie的name
String name = cookie.getName();
//获取cookie的Value值
String value = cookie.getValue();
if(name.equals("cookieName")) {
System.out.println("cookieName 对应的值:"+value);
}
}
}
}
3.2.Cookie的不足
Cookie是将数据存储在浏览器,然后每次浏览器自动会将Cookie数据放在请求头中,传递给无服务器。
1.安全性相对较差,可以在传输的过程中,修改Cookie中数据。
2.无法与其它终端共享数据。例如:用户在PC端进行了操作,数据存在Cookie中,用户在移动端是看不到用户在PC端的操作的。
3.Cookie中存储的数据有限.
3.3.Cookie的场景
1.早期时,使用Cookie做购物车。将用户的购买的商品放入Cookie中。但是在现在,使用Cookie作为购物车,无法满足需求,因为没有办法在其它终端共享数据。所以,为了共享数据,会将数据存在服务器数据库中,便于在任何平台都能看到用户的购物车,前提是用户登录了。所以,如果用户没有登陆,会将用户购物车数据存储在Cookie中,但是,当用户登录后,将Cookie中的数据保存到数据库中。
2.用户的浏览记录。一般会将用户的浏览记录放入Cookie。例如: 浏览器的访问记录.
3.使用Cookie记录用户的登录凭证,让用户免密登录。记住密码。
3.5.使用Cookie记住密码
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
登录页面
package com.sxt.controller;
import java.io.IOException;
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;
/**
* @ClassName: UserController
* @Description: @WebServlet 就是代替 web.xml中的servlet 配置
* "/user.do" 就是xml中 url-pattern的值
* @author: Mr.T
* @date: 2020年2月16日 下午2:34:42
*/
@WebServlet("/user.do")
public class UserController extends HttpServlet {
/**
* @fieldName: serialVersionUID
* @fieldType: long
* @Description: TODO
*/
private static final long serialVersionUID = 7723086583204133716L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取用户名
String userName = req.getParameter("userName");
//获取密码
String password = req.getParameter("password");
//是否记住密码
String remeber = req.getParameter("remeber");
/**
* 如果记住密码 可以将账号密码信息放入cookie 下次登录 直接从cookie中取 不用再输入账号面
*
*/
if("1".equals(remeber)) {//表示记住密码
//将账号放入cookie
Cookie userNameCookie = new Cookie("userName", userName);
//由于 cookie 默认是浏览器关闭就会消失,设置cookie的有效期
userNameCookie.setMaxAge(60*60*24*7);//1个星期
//通过resp 将cookie给浏览器
resp.addCookie(userNameCookie);
//将密码 放入cookie
Cookie passwordCookie = new Cookie("password", password);
//由于 cookie 默认是浏览器关闭就会消失,设置cookie的有效期
passwordCookie.setMaxAge(60*60*24*7);//1个星期
//通过resp 将cookie给浏览器
resp.addCookie(passwordCookie);
}
resp.getWriter().print("登录成功");
}
}