Cookie
HTTP 协议自身是属于 "无状态" 协议.
"无状态" 的含义指的是:
默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系.
但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的.
例如登陆网站成功后, 第二次访问的时候服务器就能知道该请求是否是已经登陆过了
cookie内容是什么
cookie登录过程
图中的 "令牌" 通常就存储在 Cookie 字段中.
此时在服务器这边就需要记录令牌信息, 以及令牌对应的用户信息, 这个就是 Session 机制所做的工作.
cookie从哪里来
Cookie 中存储了一个字符串,
这个数据可能是客户端(网页)自行通过 JS 写入的,
也可能来自于服务器(服务器在 HTTP 响应的 header 中通过 Set-Cookie 字段把cookie的键值对, 返回给浏览器, 之后再在本地存储).
cookie功能之身份标识
cookie可以在浏览器存储一些"临时性的数据", 其中最典型的一种使用方式就是用来存储"身份标识"
往往可以通过这个字段实现 "身份标识" 的功能.
为了实现身份识别的效果, 不仅仅需要cookie来支持, 在服务器这边也需要一个session机制来支持
后续访问网站的其他页面, 就相当于我去各个科室做检查, 都会在请求的cookie字段中, 带上刚才这里的sessionId(也就是我到了科室, 人家让我先刷就诊卡), 服务器就可以根据sessionId 就知道你的身份信息, 就知道是哪个用户在操作了.
涉及到Cookie和Session 之间的联动
cookie 的本质是浏览器在本地存储 用户自定义数据的一种关键机制
cookie存储在哪里? 怎么存?
浏览器按照不同"服务器域名"存储不同的Cookie
不同域名之间的Cookie是不能干扰的
既然是需要存储, 怎么存?
直接存储到硬盘上是不行的, 不能允许网页能够操作你的电脑文件系统, 为了保证用户上网安全, 浏览器会限制网页直接访问硬盘.
浏览器虽然禁止了直接访问硬盘, 但提供了cookie机制, 允许网页往浏览器这边存储一些自定义的键值对, 这些数据通过了浏览器提供的api 写入特定的文件中
所以cookie是间接存储在浏览器所在电脑的硬盘上
Cookie 存储往往有一个超时间, 超过时间限制就删除了
cookie的内容到哪里去?
后续再访问这个网站中的各个页面, 就会在HTTP请求中自动带上cookie, 服务器就可以进一步的知道客户端的详细资料了(刚才去看病, 各个科室刷就诊卡)
cookie在浏览器这边只能算是"暂存"
真正要让这个数据发挥作用, 还得是服务器来使用
理解会话机制 (Session)
服务器同一时刻收到的请求是很多的. 服务器需要清除的区分清楚每个请求是从属于哪个用户, 就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系.
在上面的例子中, 就诊卡就是一张 "令牌". 要想让这个令牌能够生效, 就需要医院这边通过系统记录每个就诊卡和患者信息之间的关联关系.
会话的本质就是一个 "哈希表", 存储了一些键值对结构. key 就是令牌的 ID(token/sessionId), value就是用户信息(用户信息可以根据需求灵活设计).
sessionId 是由服务器生成的一个 "唯一性字符串", 从 session 机制的角度来看, 这个唯一性字符串称为 "sessionId". 但是站在整个登录流程中看待, 也可把这个唯一性字符串称为 "token".
sessionId 和 token 就可以理解成是同一个东西的不同叫法(不同视角的叫法).
- 当用户登陆的时候, 服务器在 Session 中新增一个新记录, 并把 sessionId / token 返回给客户端. (例如通过 HTTP 响应中的 Set-Cookie 字段返回).
- 客户端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId/ token. (例如通过 HTTP 请求中的 Cookie 字段带上).
- 服务器收到请求之后, 根据请求中的 sessionId / token 在 Session 信息中获取到对应的用户信息, 再进行后续操作.
Servlet 的 Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.
Cookie 和 Session 的区别
- Cookie 是客户端的机制. Session 是服务器端的机制.
- Cookie 和 Session 经常会在一起配合使用. 但是不是必须配合.
- 完全可以用 Cookie 来保存一些数据在客户端. 这些数据不一定是用户身份信息, 也不一定是 token / sessionId
- Session 中的 token / sessionId 也不需要非得通过 Cookie / Set-Cookie 传递.
核心方法
返回值是一个数组, 每个元素是一个Cookie对象, 每个Cookie对象都是有键 也有值
HttpServletResponse 类中的相关方法
例一
@WebServlet("/getcookie") public class GetCookieServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取这次请求中的Cookie Cookie[] cookies = req.getCookies(); if(cookies != null) { for(Cookie cookie : cookies) { System.out.println(cookie.getName() + ":" + cookie.getValue()); } }else { System.out.println("请求中没有cookie ok"); } resp.getWriter().write("ok"); } }
使用费德勒进行抓包发现这这个请求里边并没有cookie
因此我们需要设置cookie
@WebServlet("/setcookie") public class SetCookieServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // t通过这个方法 设置自定义的cookie 并返回到浏览器这边 Cookie cookie = new Cookie("test", "2023-09-23"); resp.addCookie(cookie); Cookie cookie2 = new Cookie("time", "15:17"); resp.addCookie(cookie2); resp.getWriter().write("set cookie ok"); } }
当访问setcookie 请求 , 代码就会构造set- cookie字段 并放到请求响应resp中, 一并返回给浏览器
此时再访问getcookie, 就有cookie了
可以看到浏览器已经有了我们自定义的cookie
例二
sesion api 涵盖了cookie api
通过一个登录页面 进一步理解cookie 和 session 的关系
服务器后端代码如下:
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取到用户名和密码
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null || password == null || username.equals("") || password.equals("")) {
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write("请求参数不完整");
return;
}
// 2. 验证用户名密码是否正确了
// 正常的验证, 是从数据库中读取数据
// 注册账号就会给数据库插入用户名和密码. 登陆的时候就是验证当前用户名是否存在,以及密码是否匹配
// 当前为了简单, 先不引入数据库, 直接通过编码的方式来判定用户名密码
// 直接定义, 唯一合法的用户名是 zhangsan 密码是123
if(!username.equals("zhangsan")) {
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write("用户名错误!");
return;
}
if(!password.equals("123")) {
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write("密码错误");
return;
}
// 3. 登录成功 此时可以给这个用户 创建会话了
HttpSession session = req.getSession(true);
// 在会话中, 可以顺便保存点自定义的数据, 比如保存一个登录的时间戳
// setAttribute 后面的value 是一个object 想存啥都可以
session.setAttribute("username", username);
session.setAttribute("time", System.currentTimeMillis());
// 4. 让页面自动转到网站主页.
// 此处约定主页的路径是 index (也使用Servlet 生成一个动态页面)
resp.sendRedirect("index");
}
}
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 先验证一下用户的登录状态, 如果未登录, 就要求用户先登录一下
HttpSession session = req.getSession(false);
if(session == null) {
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write("请先登录, 再访问主页");
return;
}
// 已经登录成功了
// 就可以取出之前 attribute
String username = (String) session.getAttribute("username");
Long time = (Long) session.getAttribute("time");
System.out.println("username" + username + ",time" + time);
//
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write("欢迎您," + username + "! 上次登录时间" + time);
}
}
具体演示:
HttpSession session = req.getSession(true);
我们先通过login.html页面 向/login发送一个post登录请求(下图), 可见请求中 并未带有cookie 字段
而post请求返回的响应中 已经带有设定好的set-cookie字段, 这个字段就是返回给浏览器, 之后被浏览器存储为cookie
resp.sendRedirect("index");
之后重定向, 浏览器向主页面index 发送get请求, 可以看到此时请求中已经带有cookie
// 先验证一下用户的登录状态, 如果未登录, 就要求用户先登录一下
HttpSession session = req.getSession(false);
此时会话存在且有cookie, 则取cookie中的JsessionId对照服务器中的session(二者一定是一一对照的), 对照成功后返回该session
而此时新建一个页面进行登录操作, 已经带有先前设定好的cookie了
回复的响应中也不再需要set-cookie字段
还有一处细节
同一个会话session 中先后设置了不同的属性Attributes, 所以返回时间不一样
// 在会话中, 可以顺便保存点自定义的数据, 比如保存一个登录的时间戳
// setAttribute 后面的value 是一个object 想存啥都可以
session.setAttribute("username", username);
session.setAttribute("time", System.currentTimeMillis());