目录
想要了解Cookie和Session的工作流程,首先需要来了解一下什么是Cookie,什么是Session?之后我将会用一个用户登录(附加:前端+后端 代码)的栗子,让你通透整个工作流程;
有时候需要让网页存储一些简单数据,但由于网页禁止js访问电脑硬盘(原因:安全性),所以就提供了特殊的api给网页用,Cookie就是最经典的一个方案,是浏览器在本地存储数据(存储到硬盘上)的一种机制;

Cookie如何组织信息的?键值对的形式!(如下图)

Cookie是用来存放什么数据的?
特点:
注意:Cookie不是缓存,是持久化数据的手段(保存在硬盘上),缓存的数据是用来提高访问速度的;
Cookie存在于浏览器,来源于服务器;
解释:在网页中我们所观察的Cookie都是浏览器访问了某一个服务器后,服务器返回一个响应报文,在响应header中包含 一个/多个 Set-Cookie这样的资源(程序员自己在服务器代码中写的),浏览器接收响应后,就见Set-Cookie这样的数据保存到浏览器本地;
如下图Fiddler所捕捉到的Set-Cookie:

来自服务器,存储到浏览器,最后返回到服务器;
解释:当浏览器保存了cookie后,下次访问同一网站,就会把之前存在本地的Cookie作为身份标识在http请求的header给返回到服务器,服务器就知道,喔,又是你来了~就把上次加载好的数据作为响应返回给浏览器;
服务器每一时刻接收到的请求是很多的;服务器为了区分这些请求分别是哪一个用户的,就需要记录用户和该用户信息之间的对应关系,这时Session会话机制就起到关键的作用!
Session会话的本质就是一个哈希表,用来存放一些键值对;例如用户登录一个网站,Session的key就是用户名,value就是该用户的信息;
注意:Servlet 的 Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.
sessionId是由服务器生成的一个“唯一性字符串”,也可以理解为一个身份表示,通过这个,服务器就可以识别对应的用户;从session机制的角度来看,这个唯一性字符串称为 “sessionId”,但在整个登录流程来看,也可以把这个唯一字符称为 “token” ;
如下图:

Cookie是客户端机制,Session是服务器机制。
cookie存储在客户端,所以可以分析存放在本地的cookie并进行cookie欺骗,安全性较低。
session存储在服务器上,不存在敏感信息泄漏的风险,安全性较高。
cookie的数据信息存放在本地硬盘。
session的数据信息存放在服务器的内存中(一旦重启,数据就会丢失)。
cookie存储的容量较小,一般<=4KB。
session存储容量大小没有限制(但是为了服务器性能考虑,一般不能存放太多数据)。
cookie可以长期存储,只要不超过设置的过期时间,可以一直存储。
session在超过一定的时间(通常为30分钟)不使用会失效。
Cookie 只能保管ASCII字符串。
session中能够存储任何类型的数据,包括且不限于string,integer,list,map等
Cookie和Session经常一起配合使用,但不是必须配合使用。
Cookie 主要用来存储上一次访问浏览器时间、访问次数、用户身份标识。
Session 主要用来存储用户身份标识和对应的用户详细信息。
如下图:

把握一点:HttpSession就类似于HashMap~
| 方法 | 描述 |
|---|---|
| HttpSession getSession() | 当服务器获取会话: 1.若该方法的参数为true,则判断当前会话是否存在,若不存在就创建一个新的键值对保存到哈希表中,并生成sessionId返回到浏览器,若存在则返回对应的HttpSession; 2.若该方法的参数为false,则判断当前会话是否存在,若不存在就返回null,若存在就返回HttpSession; |
| Object getAttribute(String name) | 类似于HashMap的get()方法: 返回session会话中 name(key) 所对应的value; 若没有指定名称的对象,就返回null; |
| void setAttribute(String name, Object value) | 类似于HashMap的put方法: 该方法使用指定的名称绑定一个对象到该 session 会话(绑定一对key,value); |
1. loginServlet实现登录界面的服务器,登录次数通过数据库持久化保存;(如下代码)
- import com.mysql.jdbc.Connection;
- import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
-
- 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 javax.sql.DataSource;
- import java.io.IOException;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
-
- @WebServlet("/login")
- public class LoginServlet extends HttpServlet {
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("text/html; charset=utf8");
- //从客户端请求中获取用户名和密码
- String username = req.getParameter("username");
- String password = req.getParameter("password");
- //判定账户密码是否正确
- //这里假设已经注册的账户为:
- //username:zhangsan password:123
- if (!"zhangsan".equals(username) || !"123".equals(password)) {
- resp.getWriter().write("很抱歉,登录失败,您的用户名或密码输入错误!");
- return;
- }
- //登录成功
- System.out.println("" + username + "成功登录");
- //从数据库中获取该用户的登录次数
- int loginCount = 0;
- try {
- loginCount = load(username);
- } catch (SQLException e) {
- e.printStackTrace();
- }
- //由于登录成功,所以登录次数加一,保存到数据库中
- try {
- save(username, String.valueOf(++loginCount));
- } catch (SQLException e) {
- e.printStackTrace();
- }
- //设置Session
- //getSession的参数true表示若查找不到HttpSession,就会新建立一个,并生成
- //一个sessionId插入哈希表,通过Set-Cookie返回给浏览器
- HttpSession session = req.getSession(true);
- //HttpSession对象自身相当于一个哈希表,可以根据需求设置里面的参数
- session.setAttribute("username", "zhangsan");
- session.setAttribute("loginCount", loginCount);
- //设置重定向到主界面index
- resp.sendRedirect("index");
- }
-
- //修改数据库中的数据(登录次数)
- private void save(String username, String loginCount) throws SQLException {
- //创建数据源
- DataSource dataSource = SingletonLazy.getDataSource();
- //建立连接
- Connection connection = (Connection) dataSource.getConnection();
- //构造sql
- String sql = "update message set loginCount = ? where name = ?";
- PreparedStatement statement = connection.prepareStatement(sql);
- statement.setString(1, loginCount);
- statement.setString(2, username);
- //执行sql
- int ret = statement.executeUpdate();
- //打印日志
- System.out.println("ret = " + ret);
- //关闭数据库
- statement.close();
- connection.close();
- }
-
- //从数据库中获取登录次数
- private int load(String username) throws SQLException {
- //创建数据源
- DataSource dataSource = SingletonLazy.getDataSource();
- //建立连接
- Connection connection = (Connection) dataSource.getConnection();
- //构造sql
- String sql = "select loginCount from message where name = ?";
- PreparedStatement statement = connection.prepareStatement(sql);
- statement.setString(1, username);
- //执行sql
- ResultSet resultSet = statement.executeQuery();
- int loginCount = 0;
- while(resultSet.next()) {//这里只对应一个结果
- loginCount = resultSet.getInt("loginCount");
- }
- //关闭数据库
- statement.close();
- connection.close();
- return loginCount;
- }
- }
2.通过单例模式(懒汉)创建数据库的数据源(如下图)
- import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
-
- import javax.sql.DataSource;
-
- public class SingletonLazy {
- private volatile static DataSource dataSource = null;
- public static DataSource getDataSource() {
- if(dataSource == null) {
- synchronized(SingletonLazy.class) {
- if(dataSource == null) {
- dataSource = new MysqlDataSource();
- ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/login?characterEncoding=utf8&useSSL=false");
- ((MysqlDataSource)dataSource).setUser("root");
- ((MysqlDataSource)dataSource).setPassword("1111");
- }
- }
- }
- return dataSource;
- }
- }
3. indexServlet实现登录后主页面的反馈;(如下图)
- 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("/index")
- public class indexServlet extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("text/html; charset=utf8");
- //判断当前用户是否已经登录
- //getSession参数为false,表示若没有创建过HttpSession,则返回null;
- //若创建过,则返回该对象
- HttpSession session = req.getSession(false);
- if(session == null) {
- resp.getWriter().write("很抱歉,尚未登录");
- //重定向到index
- resp.sendRedirect("login");
- return;
- }
- //已登录过,就从Session中访问数据
- //这里由于getAttribute返回的是Object类型,所以这里需要强转成String
- String username = (String)session.getAttribute("username");
- int loginCount = (int)session.getAttribute("loginCount");
- resp.getWriter().write("欢迎回来:" + username + "~" + "
" - + "您今日已经登录了:" + loginCount + "次");
- }
- }
4. login.html 一个简易登录界面,用来发送post请求登录
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>登录界面title>
- head>
- <body>
- <style>
- .container {
- width: 400px;
- margin: 0 auto;
- }
- h1 {
- padding: 10px;
- width: 200px;
- margin: 0 auto;
- text-align: center;
- }
- p {
- margin: 10px auto;
- color: gray;
- text-align: center;
- }
- .row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 2px 0;
- width: 300px;
- }
- div input {
- height: 30px;
- width: 200px;
- }
- form {
- width: 300px;
- margin: 0 auto;
- }
- .button {
- margin: 0 auto;
- height: 40px;
- width: 300px;
- background-color: orange;
- border: none;
- color: white;
- }
- .button:active {
- background-color: rgb(251, 209, 130);
- }
- style>
- <div class="container">
- <h1>登录界面h1>
- <p>输入后点击登录,若信息正确自动跳转p>
- <form class action="login" method="post">
- <div class="row">
- <span>账户span>
- <input type="text" name="username">
- div>
- <div class="row">
- <span>密码span>
- <input type="password" name="password">
- div>
- <div class="row">
- <input class="button" type="submit" value="登录">
- div>
- form>
- div>
- body>
- html>
